C言語の基礎 - 関数

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

関数に1次元配列の先頭アドレスを渡す

C言語は関数に配列を渡すことができないが、代わりに配列の先頭アドレスを渡すことで、実質的に配列を渡したのと同じ動作を行うことができる。

配列の先頭アドレスを関数に渡すには、以下の2通りの記述方法がある。
なお、以下の例では、callee関数にarray配列の先頭アドレスを渡している。

callee(array);
callee(&array[0]);


また、呼び出され側の関数(callee)で配列の先頭アドレスを受け取るには、以下の2通りの記述方法がある。

void callee(int *array);
void callee(int array[]);


以下の例では、関数に1次元配列の先頭アドレスを渡している。

 #include <stdio.h>
 #include <stdlib.h>
 
 void DisplayArray01(int *array, int n);
 void DisplayArray02(int array[], int n);
 
 int main(void)
 {
    int array[] = {1, 2, 3, 4, 5};
 
    /* 一次元配列の先頭アドレスを関数に渡す */
    DisplayArray01(array, 5);
    DisplayArray02(&array[0], 5);
 
    return EXIT_SUCCESS;
 }
 
 /* 配列の内容を表示する                          */
 /* @param[in] array 表示したい配列の先頭アドレス */
 /* @param[in] n 配列の要素数                     */
 void DisplayArray01(int *array, int n)
 {
    int i;
 
    printf("配列の内容: ");
    for ( i = 0; i < n; i++ )
    {
       printf("%d ", array[i]);
    }
    printf("\n");
 }
 
 /* 配列の内容を表示する                          */
 /* @param[in] array 表示したい配列の先頭アドレス */
 /* @param[in] n 配列の要素数                     */
 void DisplayArray02(int array[], int n)
 {
    int i;
 
    printf("配列の内容: ");
    for ( i = 0; i < n; i++ )
    {
       printf("%d ", array[i]);
    }
    printf("\n");
 }



関数に2次元配列(配列の配列)を渡す

C言語は関数に配列を渡すことができないが、代わりに配列の先頭アドレスを渡すことで、実質的に配列を渡したのと同じ動作を行うことができる。

呼び出された側の関数(callee)で2次元配列を受け取るには、2通りの記述方法がある。
例えば、int array[10][5]を関数に渡す場合は、以下のように記述できる。

 void callee(int (*array)[5]);
 void callee(int array[][5]);


以下の例では、関数に2次元配列(配列の配列)を渡している。

 #include <stdio.h>
 #include <stdlib.h>
 
 void DisplayArray01(int (*array)[5], int height, int width);
 void DisplayArray02(int array[][5], int height, int width);
 
 int main(void)
 {
    int array[][5] = { {1, 2, 3, 4, 5},
                       {6, 7, 8, 9, 0}
                     };
 
    /* 二次元配列を関数に渡す */
    DisplayArray01(array, 2, 5);
    DisplayArray02(array, 2, 5);
 
    return EXIT_SUCCESS;
 }
 
 /* 配列の内容を表示する                  */
 /* @param[in] array 表示したい二次元配列 */
 /* @param[in] height 配列の高さ          */
 /* @param[in] width 配列の幅             */
 void DisplayArray01(int (*array)[5], int height, int width)
 {
    printf("配列の内容: ");
    for ( int i = 0; i < height; i++ )
    {
       for ( int j = 0; j < width; j++ )
       {
          printf("%d ", array[i][j]);
       }
    }
    printf("\n");
 }
 
 /* 配列の内容を表示する               */
 /* @param[in] array 表示する2次元配列 */
 /* @param[in] height 配列の高さ       */
 /* @param[in] width 配列の幅          */
 void DisplayArray02(int array[][5], int height, int width)
 {
    printf("配列の内容: ");
    for ( int i = 0; i < height; i++ )
    {
       for ( int j = 0; j < width; j++ )
       {
          printf("%d ", array[i][j]);
       }
    }
    printf("\n");
 }



関数から配列の先頭アドレスを返す

C言語では、ローカル変数として宣言した配列の先頭アドレスを関数から返すことはできない。
正確に言うと、返したとしても関数から抜けた時点でスタックフレーム上の変数の値は不定になる。

これを回避するには、以下の3種類の方法がある。

  • 引数から配列の先頭アドレスを受け取り、それを戻り値とする方法。
  • 配列をグローバル変数として宣言する方法。
  • 配列をstatic記憶クラス指定子を付けて宣言する方法。


呼び出し元の関数(caller)で宣言されたローカル変数は、呼び出し元の関数が終了するまで確保される。
したがって、呼び出された側の関数(callee)が終了しても変数は不定にならない。
また、グローバル変数もしくはstatic記憶クラス指定子をつけて宣言された変数は、静的領域に確保されるので、原則的にプログラムの終了時まで確保され続ける。
したがって、この場合もまた、呼び出された側の関数が終了しても変数は不定にならない。

以下の例では、関数から配列の先頭アドレスを返している。

 #include <stdio.h>
 #include <stdlib.h>
 
 int *ReturnArray01(int *array);
 int *ReturnArray02(void);
 int *ReturnArray03(void);
 
 int g_array[5];
 
 int main(void)
 {
    int array[5] = {0};
    int *array01 = nullptr;
    int *array02 = nullptr;
    int *array03 = nullptr;
 
    /* 関数から配列の先頭アドレスを返す */
    array01 = ReturnArray01(array);
    array02 = ReturnArray02();
    array03 = ReturnArray03();
 
    /* 結果を表示 */
    printf("array01 array02 array03\n");
    printf("------- ------- -------\n");
    for ( int i = 0; i < 5; i++ )
    {
       printf("%7d %7d %7d\n", array01[i], array02[i], array03[i]);
    }
 
    return EXIT_SUCCESS;
 }
 
 /* 配列に値をセットする                    */
 /* @param[in,out] array 配列の先頭アドレス */
 /* @return 配列の先頭アドレス              */
 int* ReturnArray01(int *array)
 {
    for ( int i = 0; i < 5; i++ )
    {
       array[i] = i + 1;
    }
    return array;
 }
 
 /* 配列に値をセットする       */
 /* @return 配列の先頭アドレス */
 int* ReturnArray02(void)
 {
    static int array[5];
 
    for ( int i = 0; i < 5; i++ )
    {
       array[i] = i + 1;
    }
    return array;
 }
/* 配列に値をセットする      */
* @return 配列の先頭アドレス */
int* ReturnArray03(void)
{
   for ( int i = 0; i < 5; i++ )
   {
      g_array[i] = i + 1;
   }
   return g_array;
}
</syntaxhighlight>



プログラム正常終了時に関数を呼び出す

C言語に於いて、プログラム正常終了時に関数を呼び出すには、stdlib.hのatexit関数を使用して、事前に関数を登録しておく必要がある。

atexit関数は、funcが指す関数をプログラムの正常終了時に呼び出される関数として登録する関数である。
atexit関数は、関数の登録に成功した場合は0を、登録に失敗した場合は0以外を返す。

 #include <stdlib.h>
 
 int atexit(void (*func)(void));


以下の例では、atexit関数を使用して関数を登録しておくことで、正常終了時に登録された関数を呼び出している。

 #include <stdio.h>
 #include <stdlib.h>
 
 void ShowGoodBye01(void); /* 関数1 */
 void ShowGoodBye02(void); /* 関数2 */
 
 int main(void)
 {
    /* 関数を登録 */
    atexit(ShowGoodBye01); /* 関数1を登録 */
    atexit(ShowGoodBye02); /* 関数2を登録 */
 
    /* 正常終了 */
    exit(EXIT_SUCCESS);
 }
 
 void ShowGoodBye01(void)
 {
    printf("Good bye 01\n");
    fflush(stdout);
 }
 
 void ShowGoodBye02(void)
 {
    printf("Good bye 02\n");
    fflush(stdout);
 }