LoginSignup
8
5

More than 5 years have passed since last update.

【C言語】GCC拡張によるNested Function(関数内関数)

Posted at

はじめに

以下のGCCマニュアルに書かれていることを暇つぶしに書き連ねただけです
https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions
詳しく知りたい方はそちらを読んでいただいた方が早いかもしれません

概要

C言語では関数内で関数の定義をすることは出来ません

#include <stdio.h>
void func(){
    void test(){    //エラー
       puts("hoge");
    }
    test();    //エラー
}

しかし、GCC拡張を使うと上記のような記述が可能になります

※この機能はC言語でのみ利用可能です、C++では使用できません
大人しくclassやstd::functionやlambdaを使いましょう

ルール

スコープ

基本的なスコープのルールと殆ど同じです

int sp = 10;        //グローバル変数
static int sp2 = 20;    //static変数
extern int sp3;     //extern宣言

int sum(int v1,int v2){return v1 + v2;}

void func(int value){
    int value2 = 0;

    void test(){
        int ex = 10;
        value = sum(value,value2);  //OK:value,value2,sumはスコープ範囲内
        value2 += sp + sp2 + sp3;   //OK:sp,sp2,sp3はスコープ範囲内
        value2 += ex;
    }
    test();
    value2 += ex;           //エラー:exはスコープ範囲外
}

int sp3 = 30;

void hoge(){
    func(100);
}

また、宣言された関数を外で使用する事はできません

void func(){
    void hoge(){puts("(´・ω・`)");}
}
int main(){
    hoge();    //エラー:hogeはスコープ範囲外
}

関数ポインタ

ネストされた関数からは関数ポインタを得ることが出来ます

void (*func(void)){
    void test(){puts("(´・ω...:.;::..");}
    return test;
}
void hoge(){
    void(*f)() = func();
    f();
}

以下のようにスコープ外の変数を参照する関数ポインタも取得できますが、
生存期間が終了する変数を参照する関数の呼び出しは危険です(曰く「all hell breaks loose」)

int (*func())(){
    int value = 100;
    int value2 = 39;
    int test(){return value + value2;}
    return test;
}
void hoge(){
    int (*f)() = func();
    int result = f();
    printf("%d\n",result);    //可能だけど危険
}

ただし、スコープ外ではあるものの、生存期間が継続している変数を参照する物に関しては大丈夫(だろう)との事

func.c
static int value = 100;
static int value2 = 39;

int (*func())(){
    int test(){return value + value2;}
    return test;
}
call.c
int (*func())();

void hoge(){
    int (*test)() = func();
    printf("%d\n",test());  //大丈夫なはず
}

リンケージ

ネストされた関数はリンケージを持ちません

extern int test();
//static int test();

void func(){
    int test(){puts("( ´・ω・)y━。o ○");}    // リンケージを持たないので、externやstaticで宣言された識別子との結合は行われない
}
void hoge(){
    test();    //エラー testの実体が存在しない
}

宣言

関数内で関数の前方宣言をする事は出来ません
またリンケージを持たないので、externで代用する事も不可能です(そんな事をする人は居ないかもしれませんが…)

void func(){
    int test(); //単純にエラー
    extern int test();
    test();
    int test(){puts("Hoge");} //エラー リンケージを持たない宣言とexternが同居してる
}

関数内でどうしても前方宣言が必要な場合は、autoを使用します

void func(){
    auto int test();
    test();
    int test(){puts("Hage");}
}

goto

ネストされた関数は、GCCのローカルラベルを使用してブロック外のラベルにジャンプできます

void func(){
    __label__ label;
    int test(){goto label;}
    test();
        return;
    label:
    puts("Jump!");
}

記事内サンプルソースコードの確認環境

OS:Windows10 Pro(64bit)
GCC:7.3.0(MSYS2)
コンパイル時オプション:特になし

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5