C言語のgoto文って必要なの?
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
goto文
goto文は,コード中の指定された場所(ラベル)に無条件にジャンプ(移動)する制御構造を持つ文です.
goto文のメリットは,自由度が高いコードを書けることです.
goto文の使い方は以下になります.
1 2 3 4 5 6 7 |
void func(void) { label: int i = 0; ... goto label; } |
goto文のデメリットは,どこでもジャンプできることでコードが複雑になり,保守性が低くなることです.
例えば,アップル社の製品(iOS7,OSX,Safari)ではgoto文を利用したことによる脆弱性が発生しています.
なので,goto文は一般的には禁じ手と言われています.
そうすると,そもそもgoto文がなんであるのか疑問に思いますよね?
そこで,本記事ではC言語でgoto文が有用な3つの例外を紹介していきます.
また,C言語で開発された代表的なOS「Linux」におけるgoto文の利用例も取り上げます.
Linuxカーネルを学びたいあなたは,こちらからどうぞ.
C言語でgoto文が有用な3つの例外
C言語でgoto文が有用な3つの例外は以下になります.
- 資源の獲得・解放を1:1対応にする場合
- 多重構文から脱出する場合
- 依存関係がある資源を獲得・解放する場合
資源の獲得・解放を1:1対応にする場合
資源の獲得・解放を1:1対応にする場合にgoto文を利用します.
C言語では,資源を獲得したら明示的に解放しなければなりません.
例えば,ファイルをfopen関数でオープンしたらfclose関数でクローズ,メモリをmalloc関数で獲得したらfree関数で解放します.
以下にメモリをmalloc関数で獲得する例を紹介します.
このコードはfunc関数でmalloc関数を呼び出してメモリを獲得していますが(14行目),free関数でメモリを解放していないのでメモリリークを起こしてしまいます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #define BUFSIZE 65536 void func(size_t size, int x) { void *p; if ((p = malloc(size)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes.\n", size); return; } if (x == 0) { return; } /* do something. */ } int main(void) { func(BUFSIZE, 0); return 0; } |
なので,free関数を呼び出す正しいコードは以下になります.
このコードでは,malloc関数は14行目の1ヶ所で呼び出すのに対して,free関数は20行目と26行目の2ヶ所で呼び出さなくてはなりません.
よく読めば正しいコードだとわかりますが,パット見だと間違っているのでは?と疑わしいコードになってしまいます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #define BUFSIZE 65536 void func(size_t size, int x) { void *p; if ((p = malloc(size)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes.\n", size); return; } if (x == 0) { free(p); return; } /* do something. */ free(p); } int main(void) { func(BUFSIZE, 0); return 0; } |
なので,malloc関数とfree関数の呼び出しを1:1対応にするために,goto文を利用して書き直した例が以下になります.
malloc関数は14行目の1ヶ所,free関数は26行目の1ヶ所でそれぞれ呼び出しています.
19行目のif文の返り値が0の場合,20行目で「goto end;」と書くことで25行目の「end:」にジャンプします.
このようにgoto文を利用することで,資源の獲得・解放を1:1対応にできます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #define BUFSIZE 65536 void func(size_t size, int x) { void *p; if ((p = malloc(size)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes.\n", size); return; } if (x == 0) { goto end; } /* do something. */ end: free(p); } int main(void) { func(BUFSIZE, 0); return 0; } |
Linuxでは,kernel/sched/core.cファイルのwake_up_if_idle関数でgoto文が利用されています.
資源の獲得は6行目のrcu_read_lock関数,資源の解放は22行目のrcu_read_unlock関数で実行しています.
9行目の「goto out;」で,21行目の「out:」にジャンプします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void wake_up_if_idle(int cpu) { struct rq *rq = cpu_rq(cpu); struct rq_flags rf; rcu_read_lock(); if (!is_idle_task(rcu_dereference(rq->curr))) goto out; if (set_nr_if_polling(rq->idle)) { trace_sched_wake_idle_without_ipi(cpu); } else { rq_lock_irqsave(rq, &rf); if (is_idle_task(rq->curr)) smp_send_reschedule(cpu); /* Else CPU is not idle, do nothing here: */ rq_unlock_irqrestore(rq, &rf); } out: rcu_read_unlock(); } |
多重構文から脱出する場合
多重構文から脱出する場合にgoto文を利用します.
以下のコードでは,while文が13~24行目,switch文が16~23行目にあります.
scanf関数で数字を入力し,その数字がEXIT_NUMBERと等しい場合,18行目の「goto end;」で26行目の「end:」にジャンプします.
もし18行目をgoto文ではなくbreak文にする場合,switch文から脱出するだけになり,while文を再度実行してしまうことに注意して下さい.
※もちろんフラグを設定してif文を追加すれば多重構文から脱出できますが,冗長なコードになってしまいます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define EXIT_NUMBER 23 void func(void) { int c; while (1) { scanf("%d", &c); switch (c) { case EXIT_NUMBER: goto end; default: printf("%d\n", c); break; } } end: return; } int main(void) { func(); return 0; } |
Linuxでは,fs/ocfs2/super.cファイルのocfs2_parse_options関数でgoto文が利用されています.
while文が28~63行目,switch文が33~62行目にあります.
46行目で「goto bail;」で,79行目の「bail:」にジャンプします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
static int ocfs2_parse_options(struct super_block *sb, char *options, struct mount_options *mopt, int is_remount) { int status, user_stack = 0; char *p; u32 tmp; int token, option; substring_t args[MAX_OPT_ARGS]; trace_ocfs2_parse_options(is_remount, options ? options : "(none)"); mopt->commit_interval = 0; mopt->mount_opt = OCFS2_MOUNT_NOINTR; mopt->atime_quantum = OCFS2_DEFAULT_ATIME_QUANTUM; mopt->slot = OCFS2_INVALID_SLOT; mopt->localalloc_opt = -1; mopt->cluster_stack[0] = '\0'; mopt->resv_level = OCFS2_DEFAULT_RESV_LEVEL; mopt->dir_resv_level = -1; if (!options) { status = 1; goto bail; } while ((p = strsep(&options, ",")) != NULL) { if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case Opt_hb_local: mopt->mount_opt |= OCFS2_MOUNT_HB_LOCAL; break; case Opt_hb_none: mopt->mount_opt |= OCFS2_MOUNT_HB_NONE; break; case Opt_hb_global: mopt->mount_opt |= OCFS2_MOUNT_HB_GLOBAL; break; case Opt_barrier: if (match_int(&args[0], &option)) { status = 0; goto bail; } if (option) mopt->mount_opt |= OCFS2_MOUNT_BARRIER; else mopt->mount_opt &= ~OCFS2_MOUNT_BARRIER; break; /* omitted... */ default: mlog(ML_ERROR, "Unrecognized mount option \"%s\" " "or missing value\n", p); status = 0; goto bail; } } if (user_stack == 0) { /* Ensure only one heartbeat mode */ tmp = mopt->mount_opt & (OCFS2_MOUNT_HB_LOCAL | OCFS2_MOUNT_HB_GLOBAL | OCFS2_MOUNT_HB_NONE); if (hweight32(tmp) != 1) { mlog(ML_ERROR, "Invalid heartbeat mount options\n"); status = 0; goto bail; } } status = 1; bail: return status; } |
依存関係がある資源を獲得・解放する場合
依存関係がある資源を獲得・解放する場合にgoto文を利用します.
以下のコードでは,malloc関数とfree関数をそれぞれ3回呼び出しています.
goto文を利用することで,依存関係がある資源を獲得・解放するコードをスッキリ書けます.
※各々の資源の獲得・解放を1:1対応にしつつ多重構文を利用しないコードにもなっています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #define BUFSIZE 65536 int func(void) { void *a, *b, *c; int ret = 0; if ((a = malloc(BUFSIZE)) == NULL) { ret = 1; goto error_a; } if ((b = malloc(BUFSIZE)) == NULL) { ret = 2; goto error_b; } if ((c = malloc(BUFSIZE)) == NULL) { ret = 3; goto error_c; } /* do something. */ free(c); error_c: free(b); error_b: free(a); error_a: return ret; } int main(void) { printf("ret = %d\n", func()); return 0; } |
Linuxでは,kernel/sched/core.cファイルのsched_tick_remote関数でgoto文が利用されています.
資源の獲得は22行目のrq_lock_irq関数,資源の解放は41行目のrq_unlock_irq関数で実行しています.
19行目のif文が偽の場合,20行目のgoto out_requeue;で42行目のrequeueラベルにジャンプします.
25行目のgoto out_unlock;で,40行目のout_unlock:にジャンプします.
つまり,19行目のtick_nohz_tick_stopped_cpu関数とrq_lock_irq関数の呼び出しが依存関係になっています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
static void sched_tick_remote(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); struct tick_work *twork = container_of(dwork, struct tick_work, work); int cpu = twork->cpu; struct rq *rq = cpu_rq(cpu); struct task_struct *curr; struct rq_flags rf; u64 delta; int os; /* * Handle the tick only if it appears the remote CPU is running in full * dynticks mode. The check is racy by nature, but missing a tick or * having one too much is no big deal because the scheduler tick updates * statistics and checks timeslices in a time-independent way, regardless * of when exactly it is running. */ if (!tick_nohz_tick_stopped_cpu(cpu)) goto out_requeue; rq_lock_irq(rq, &rf); curr = rq->curr; if (cpu_is_offline(cpu)) goto out_unlock; update_rq_clock(rq); if (!is_idle_task(curr)) { /* * Make sure the next tick runs within a reasonable * amount of time. */ delta = rq_clock_task(rq) - curr->se.exec_start; WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 3); } curr->sched_class->task_tick(rq, curr, 0); calc_load_nohz_remote(rq); out_unlock: rq_unlock_irq(rq, &rf); out_requeue: /* * Run the remote tick once per second (1Hz). This arbitrary * frequency is large enough to avoid overload but short enough * to keep scheduler internal stats reasonably up to date. But * first update state to reflect hotplug activity if required. */ os = atomic_fetch_add_unless(&twork->state, -1, TICK_SCHED_REMOTE_RUNNING); WARN_ON_ONCE(os == TICK_SCHED_REMOTE_OFFLINE); if (os == TICK_SCHED_REMOTE_RUNNING) queue_delayed_work(system_unbound_wq, dwork, HZ); } |
まとめ
C言語でgoto文が有用な3つの例外を紹介しました.
- 資源の獲得・解放を1:1対応にする場合
- 多重構文から脱出する場合
- 依存関係がある資源を獲得・解放する場合
これらの場合でgoto文を利用すると正しいコードをスッキリ書けますので,goto文の利用を検討してみてはいかがでしょうか.
goto文は以下の自作関数で利用していますので,実際のコードを知りたいあなたは,これらの記事を読みましょう!
goto文は同じ関数内でしかジャンプできませんが,他の関数にジャンプしたい場合はsetjmp/longjmp関数を利用します.
setjmp/longjmp関数の使い方を学びたいあなたはこちらからどうぞ.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!