C言語入門 第11回 関数とデータの受け渡し、変数の種類




2014年10月より個人の方を対象に、Study C無料提供を開始しました。
C言語を勉強中の方は、学習・教育に最適なC言語インタープリタのStudy Cを使ってみてください(個人の方は無料です)。
大学・高専・高校などの教育機関での採用実績も多数あるロングセラー商品Study Cが、個人向けに無料提供を始めました。
インタープリタの手軽さに加え、ゲームや3Dタートルグラフィックで楽しく勉強したりと、C言語の学習を強力にサポートします。
ブロック崩しゲーム 3Dツリー クリスマスツリー
また、このようなボタンの用意されているページでは、掲載しているプログラムをStudy Cに直接ロードし実行したりすることができます。
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する
Study C無料利用についての詳細は、このページを参照してください。



1.関数とデータの受け渡し

 C言語のプログラムは、すべて関数形式で組み立てられていきます。このため関数間のデータ受け渡しが重要な働きをします。 データ受け渡し方法はこれまでのC言語入門で説明しました。この方法をまとめると次のようになります。

 @呼出し側から関数へのデータ受け渡しにはパラメータを使用します。 パラメータとは関数名の後のカッコ内に書かれているデータ受け渡し用の変数を指します。 パラメータは使用する環境で異なりますが、30個程度まで使用することができます。

 A関数が計算した値をreturn文で呼出し側に返すことができます。この方法によって返される値を返り値と呼びます。

 多くのプログラムでは、これらの方法だけで関数間のデータ受け渡しを十分行うことができます。しかし、一つの関数が2個以上の値を呼出し側に返すことができません。 たとえば、「与えられた2つの数で割り算を行い、商と余りを返す関数」を作ることができません。今まで説明した方法だけでこの関数を作ると、次のようになります。

int     syou(int, int);
int     amari(int, int);

main()
{
        int a, b;

        a = syou(10, 3);
        b = amari(10, 3);
        printf("%d\n%d\n", a, b);
}

syou(int x, int y)
{
        return (x/y);
}

amari(int x, int y)
{
        return (x%y);
}
3
1
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

 x%yはxをyで割った余りを計算するものです。たとえば、10%3の結果は1になります。

 一つの関数で2個の値を返すことができないため、2つの関数に分けました。しかし、次のような場合には、関数を分けてしまうのは賢明ではありません。

func(int x, int y)
{
        int a, b, c;

        x, yを使って長く複雑な計算を行う。
        3つの計算結果が変数a, b, cに格納される。
        a, b, cの値をすべて呼出し側に返したいが、
        1個しか返せないのでaだけ返す。
        return (a);
}

b, cについても値を返したいので次の関数をfunc1, func2を追加します。
func1(int x, int y)
{
        int a, b, c;
        func()と同じ計算を行う。
        return (b);
}

func2(int x, int y)
{
        int a, b, c;
        func(), func1()と同じ計算を行う。
        return (c);
}

このように、明らかに効率の悪いプログラムになってしまいます。関数間で複数のデータを交換するには次の2つの方法を使用します。

 @グローバル変数の使用
 Aポインタの使用(今後のC言語入門で説明します)


2.データ受け渡しの失敗例

 今回のC言語入門では、@のグローバル変数の使用方法を説明しますが、その前にデータ受け渡しの失敗例を検討してみましょう。

int     func(int, int);

main()
{
        int a, b;

        func(10, 3);
        printf("%d\n%d\n", a, b);
}

func(int x, int y)
{
        a = x / y;
        b = x % y;
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

関数func()は与えられた数の商と余りを求める関数です。この例では関数main()内でのみ使用可能な変数a, bに関数main()内で結果を代入しようとしています。  しかし、このプログラムを実行すると、

a = x / y;
b = x % y;

の2行で変数a, bが未定義(宣言されていない)というエラーが起きてしまいます。関数main()内の宣言(int a, b;)は、その関数内でしか有効ではないのです。このエラーに対処するため、プログラムを次のように変更します。

int     func(int, int);

main()
{
        int a, b;

        func(10, 3);
        printf("%d\n%d\n", a, b);
}

func(int x, int y)
{

        int a, b;

        a = x / y;
        b = x % y;
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

 これで先ほどのエラーは発生しなくなりますが、実行結果はでたらめな値しか表示されません。関数main()と関数func()で宣言されている変数a, bは名前が同じだけで、まったく別の存在です。

main家
+---------+
| aさん  |
| bさん  |
+---------+
main家のaさんbさんには
何も連絡がない

func家
+---------+
|  aさん  |
|  bさん  |
+---------+
func()家のaさんとbさんに
商と余りを教えている
 もう一つ失敗例を検討します。
int     func(int, int, int, int);

main()
{
        int  a, b;

        func(10, 3, a, b);
        printf("%d\n%d\n", a, b);
}

func(int x, int y, int a, int b)
{
        a = x / y;
        b = x % y;
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

このプログラムではパラメータを使ってデータを交換しようとしています。これも前の失敗例と同じく変数a, bが名前の同じ別人のため、データは関数main()へは返されません。 しかし、func()を呼出すときに変数a, bをパラメータとして指定しているため、両関数の変数a, bに接点があるように見え、func()からmain()へデータを返すことができると思われがちです。 これを理解するために、C言語がどのように関数を呼出し、実行するのかを見てみましょう。

 @関数の呼出しは、関数名(式, ・・・)の形式で行われます。このため、最初にカッコ内の式を計算して値を求めます。たとえば、

a = 10;
b = 20;
func(5*2, 3, a+1, b);

では、次のように変換されてから関数呼出しが行われます。

func(10, 3, 11, 20);

このため関数func()が呼び出されるときにはa, bといった変数のことは忘れられてしまい、変数内に蓄えられている値だけが利用されます。

 A呼び出された側はカッコ内の値を変数に代入します。関数宣言が

func(int x, int y, int a, int b)
{
        .
        .
        .
}

のようならば、カッコ内の値(10, 3, 11, 20)はそれぞれ変数x, y, a, bに代入されます。この、代入が行われるという点がパラメータ用変数と普通の変数の異なる点です。

func(int x)             func()
                        {
{                               int     x;
}                       }

パラメータ用変数x       普通の変数xには
には呼出し時に         代入は行われないので、
何らかの値が            でたらめな値が格納
代入される。            されている。

 B与えられた数などをもとに計算を行い、return (式);という行があれば呼出し側にデータを1つだけ返します。

 このように、パラメータ用変数は普通の変数とほとんど違いがないので、パラメータ用変数を使ってデータ(計算結果)を返すことはできません。



3.グローバル変数

 今まで普通の変数といっていたものは、1つの関数内でしか使用できないことからローカル(局所的な)変数と呼ばれています。これに対していくつもの関数で使用できる変数があり、これをグローバル(広域的な)変数と呼びます。 変数をグローバルかローカルにするかは宣言を行う場所によって異なります。次のプログラムを見てください。

int g;
                                        -+
main()                                   |
{                                        |
        int l1; +- 変数l1はこの範囲で    
        .       |  利用可能              
        .       |                        変数gはこの宣言
        .       |                        以降のプログラム
}               +-                       で利用可能
                                         
                                         
func()                                   
{                                        
        int l2; +- 変数l2はこの範囲で    
        .       |  利用可能              
        .       |                        |
        .       |                        |
}               +-                      -+

変数gはグローバル変数、l1, l2がローカル変数に相当します。変数宣言の相違点は、{〜}の内側で宣言されているかどうかだけです。{〜}の外側で宣言されるグローバル変数は複数の関数で利用可能なので、データの受け渡しが可能です。 失敗例のプログラムをグローバル変数を使って書き換えます。

int     func(int, int);

int a, b;

main()
{
        func(10, 3);
        printf("%d\n%d\n", a, b);
}

func(int x, int y)
{
        a = x / y;
        b = x % y;
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

このプログラムでは、変数a, bをグローバル変数として宣言しています。このため、変数a, bは関数main()からも関数func()からも利用することができます。こうして関数func()から2つのデータを関数main()に返すことが可能になります。

 グローバル変数とローカル変数は同じ名前で宣言することもできます。ローカル変数が宣言されている関数内では一時的にグローバル変数の存在が隠されてしまいます。 グローバル変数に代入したつもりが実際にはローカル変数だったりするため、同じ名前の使用は避けたほうが良いでしょう。func()以外ではグローバル変数aが使用されます。

int a;

main()
{
        .
        .
        .
}


func()
{               -+ この範囲では
        int a;   | 変数aが使用される
        .        |
        .        |
        .        |
}               -+

参考)
 単純な関数間のデータ受け渡しにグローバル変数を多用すべきではありません。 グローバル変数はいくつもの関数が共通に利用するデータを厳選し、最少限のグローバル変数しか使用しないように心がけましょう。 C言語はそれぞれの関数を独立した部品としてつくり、それを組み立てることでプログラムを作っていきます(このような作り方をモジュール化と呼びます。 また、関数のことをモジュールと呼ぶこともあります)。 グローバル変数を多用することは、この考え方に反するものになってしまいます。


4.自動変数と静的変数

 変数をグローバル/ローカルに分類しましたが、両者は自動(auto)、静的(static)という別の分類方法とも密接に関係しています。

 まず、自動変数について説明します。自動変数は関数内のローカル変数である必要があります。 そして自動変数は関数が呼び出されたときに作り出され、関数の終了と同時に消え去ってしまいます。 一方、静的変数はプログラムの実行開始から終了まで存在する普通の変数です。 したがって関数外で宣言するグローバル変数は、プログラムの実行開始から終了までいつでも使える必要があり、静的変数に分類されます。 また、関数内で宣言するローカル変数は今までのところすべて自動変数に相当していました。 しかし、ローカル変数は静的変数として宣言することも可能です。

関数内での静的変数、自動変数の宣言方法

static char    ...;
static int     ...;

auto char      ...;
auto  int       ...;

 静的/自動変数は今まで説明してきた宣言の最初にautoかstaticを付けることで宣言します。 また、auto、staticを省略した宣言は関数内ではauto、関数外では静的を付けた場合と同じくなります。

関数外          static宣言(省略時)      静的変数
                auto宣言                エラー

関数内          static宣言              静的変数
                auto宣言(省略時)        自動変数

関数パラメータ  static宣言              エラー
                auto宣言(省略時)        自動変数

 自動/静的変数は今の段階では理解していなくてもよい知識ですが、次のような誤りの原因となるため説明しました。

int     add(int);

main()
{
        add(1);
        add(2);
        add(3);
        printf("%d\n", add(4));
}

add(int n)
{
        int sum;

        sum += n;
        return (sum);
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

このプログラムは関数addに与えた数をつぎつぎと加算していき、合計を求めるプログラムです。しかし、このプログラムは実行するたびに異なったでたらめな値を表示します。 この原因は、変数sumが自動変数であるために関数addが終了するたびに変数sumが消滅して内容が消えてしまうためです。

int     add(int);

main()
{
        add(1);
        add(2);
        add(3);
        printf("%d\n", add(4));
}

add(int n)
{
        static int sum;         ←static変数宣言に変更

        sum += n;
        return (sum);
}
10
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

 int sum;をstatic int sum;とすれば正常に動作するようになります。 このようにローカル変数もすべて静的変数として宣言してしまえば思った通りに動作することが多いのですが、自動変数は記憶場所を有効に利用してくれるので、必要なときだけ静的変数を使うのがよいでしょう。 また、自動変数には実行時に内容が0になっていますが、自動変数にはでたらめな値が入っています。

int g;

main()
{
        static int s;
        auto int a;

        printf("global = %d\n", g);
        printf("static = %d\n", s);
        printf("auto = %d\n", a);
}
global = 0
static = 0
auto = でたらめな値
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

このため、自動変数は使用する前に必ず何らかの値を代入しておかなければなりません。 次のプログラムは1から10までの合計を求めるプログラムの失敗例です。

main()
{
        int sum;
        int i;

        for (i = 1; i <= 10; i++) {
                sum += i;
        }
        printf("%d\n", sum);
}
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

 このプログラムを実行しても正しい値は表示されません。原因は変数sumを使う前に代入を行っていないためです。

main()
{
        int sum;
        int i;

        sum = 0;        ←この行を追加
        for (i = 1; i <= 10; i++) {
                sum += i;
        }
        printf("%d\n", sum);
}
55
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する ブラウザとの連携機能が使用可能なStudy Cのバージョンなどについて...

 今回のC言語入門ではふれませんが、関数外で宣言するグローバル変数にstaticをつけると厳密には同じ意味の宣言ではなくなってしまいます。 このためstaticという単語は、このC言語入門で説明したのと異なる意味で使われることがあるので注意してください。 static宣言については今後のC言語入門で説明する予定です。


[an error occurred while processing this directive]