C言語の配列の宣言方法: 配列、可変長配列、内部結合な配列、外部結合な配列、ポインタ配列
- 作成日: 2021-11-25
- 更新日: 2023-12-25
- カテゴリ: C言語
C言語の配列の宣言方法
C言語では配列を使うことができます。
この記事ではC言語の配列の宣言方法について解説します。
配列はC言語のプログラミングで頻繁に使われるデータ型の1つです。
そのため配列の使い方、配列の宣言方法を把握しておくのは非常に有用と言えます。
配列はさまざまなシーンで使うことができるからです。
この記事では具体的にC言語の配列について↓を見ていきます。
- 宣言とは?
- 宣言と定義の違い
- 配列の宣言方法
- 宣言した配列の要素の値はどうなっているか
- 配列の宣言位置の注意点
宣言とは?
C言語における「宣言」とはいったい何でしょうか?
宣言とは「これからこの変数、配列を使うよ」というコンパイラにするお知らせです。
C言語では宣言をしていない変数、配列は使うことができません。
かならず宣言が必要です。
C言語で配列を使う場合はまず最初にこの宣言をする必要があります。
宣言をしていない配列は使うことができないので注意が必要です。
宣言の制約
C言語の宣言には「制約」と呼ばれる約束事があります。
「宣言を使うならこれを守ってよね」というものです。
その制約とは↓のようなものです。
- 基本的には同じスコープの中で同じ名前が2つ以上あってはならない
- 型を指定しなければならない
C言語で配列を宣言するには↑のような制約を守る必要があります。
同じスコープ(ブロック)で同じ名前の配列を複数宣言したり、型を指定せずに配列を宣言することはできません。
ただしグローバルな変数、配列の宣言においては「同じ名前が2つ以上あってはならない」という制約は適用されません。
関数内の変数、配列の宣言では同じスコープ(ブロック)上で同じ名前は2つ以上あってはいけません。
また関数内の変数、配列においてもextern
指定子を付けた場合はこの制約は適用されません。
宣言ではなく定義の場合はグローバルな変数、配列においても同じ名前が2つ以上あったらエラーになります。
この辺はけっこうややこしいです。
宣言と定義の違い
宣言と定義の違いについてです。
定義とは、変数や配列の記憶域(メモリ)を確保することを言います。
宣言がコンパイラに変数や配列の使用をお知らせするのに対して、定義は実際に変数や配列のメモリを確保します。
定義は宣言に含まれます。
定義は宣言のサブセットです。
つまり定義した状態は宣言した状態でもあることになります。
宣言 > 定義
C言語において宣言と定義を見分けるのはけっこう難しいです。
メモリが確保されているかどうか? という点で宣言か定義かを判断するのがいいです。
配列の宣言方法
C言語で配列を宣言する方法を解説します。
配列にはさまざまな種類がありますが、ここでは↓を解説していきたいと思います。
- 配列の宣言のフォーマット
- 配列の宣言の制約
- 普通の配列の宣言
- 可変長配列(VLA)の宣言
- 内部結合な配列(静的配列、static)の宣言
- 外部結合な配列(extern)の宣言
- 定数な配列の宣言
- ポインタ配列の宣言
配列の宣言のフォーマット
C言語の配列の宣言のフォーマットは↓になります。
# 最も基本的なフォーマット
型 配列名[要素数を決定する式];
# 型修飾子と記憶域クラス指定子の組み合わせ
型修飾子 型 配列名[要素数を決定する式];
記憶域クラス指定子 型 配列名[要素数を決定する式];
記憶域クラス指定子 型修飾子 型 配列名[要素数を決定する式];
最初に型を書き、それから配列名を書きます。
そして角カッコを書いてその中に要素数を決定する式を書きます。
コードにすると↓になります。
int iary[10];
const short saryr1[10 * 2];
volatile short sary2[10 * 2];
static float fary[10 / 2];
static const double dary[10 - 2];
extern char cary[10];
配列の宣言には型の他に型修飾子と記憶域クラス指定子を付けることも出来ます。
「型修飾子」には↓のキーワードが含まれます。
- const
- restrict
- volatile
「記憶域クラス指定子」には↓のキーワードが含まれます。
- typedef
- extern
- static
- auto
- register
ただし型修飾子のrestrict
は配列の宣言に使うことはできません。
↓のようにエラーになります。
restrict int iary[10];
// GCC: error: invalid use of ‘restrict’
配列の宣言の制約
宣言には制約がありましたが、配列の宣言にも制約があります。
それは↓のようなものです。
- 配列の大きさを指定する式の型は整数型でなければならない
- 要素の型が不完全型または関数型であってはならない
配列の要素数の決定には式を使うことができます。
その式の結果は整数型である必要があります。
つまり↓のようなコードはだめです。
int ary[1.2 * 3.4];
// GCC: error: size of array ‘ary1’ has non-integer type
不完全型とはANSI Cでは↓のどれかになります。
- void
- 不特定長の配列
- 不特定内容の構造体と共用体
↓のようにvoidで配列は宣言できません。
void ary[10];
// GCC: error: declaration of ‘ary’ as array of voids
voidは不完全型なので配列の宣言には使えませんが、voidのポインタ型は配列の宣言に使えるので注意が必要です。
それから関数型とは関数のことです。
関数ポインタではないので注意してください。
関数ポインタは配列にすることが可能です。
普通の配列の宣言
いわゆる関数内などで宣言される普通の配列です。
↓のように宣言します。
int main(void) {
int ary1[10];
int ary2[10 * 2];
return 0;
}
↑はint型の配列ary1
とary2
の宣言です。
いわゆる普通の配列の宣言は関数内、グローバル領域の両方で行うことができます。
可変長配列(VLA)の宣言
C99以降のC言語では可変長配列(VLA)という配列を使うことができます。
これは配列の要素数が動的に決定される配列のことです。
たとえば↓を見てください。
int main(void) {
int n = 10;
int ary[n];
return 0;
}
↑の配列ary
が可変長配列です。
この配列の要素数は変数n
で動的に決定されています。
内部結合な配列(静的配列、static)の宣言
記憶域クラス指定子のstatic
を使うと内部結合(内部リンケージ)な配列を宣言できます。
この指定子を使った配列は関数内、グローバル領域の両方で宣言できます。
static int gary[10];
int main(void) {
static int ary[10];
return 0;
}
static
をつけた配列はそのソースコード内のみで参照できます。
これを内部結合と言います。
外部結合な配列(extern)の宣言
ソースファイルが複数あって、別のソースファイル内で宣言されているグローバルな配列をこっちのソースファイル内で使いたい場合は配列にextern
を付けて宣言します。
たとえば↓のような2つのソースファイルがあるとします。
// other.c
int ary[3] = {1, 2, 3};
// extern.c
#include <stdio.h>
extern int ary[3];
int main(void) {
printf("%d\n", ary[0]); // 1
return 0;
}
これらのファイルをGCCコンパイラで↓のようにコンパイルして実行します。
$ gcc extern.c other.c
$ ./a.out
1
other.c
の配列ary
はグローバルな配列です。
これをextern.c
内で参照するには↑のようにextern int ary[3];
と宣言します。
そうするとother.c
のary
をextern.c
から参照することができます。
定数な配列の宣言
型修飾子のconst
を使うと不変な配列を宣言できます。
int main(void) {
const int ary[3];
return 0;
}
const
を付けた場合、const
はその直後か直前の型を修飾します。
↑の場合はconst
はint
を修飾しています。
つまり配列のint
の要素を不変にしているわけです。
こうするとコンパイラレベルで配列の要素を不変に出来ます。
配列の要素に代入しようとすると↓のようにエラーになります。
const int ary[3];
ary[0] = 10;
// GCC: error: assignment of read-only location ‘ary[0]’
もっとも、このような宣言のみの配列にconst
を付ける意味はほとんど無いかもしれません。
🦝 < 使いどころがないよね
🐭 < いや、あるかもしれない
ポインタ配列の宣言
int
型のポインタの配列を宣言する場合は↓のようにします。
int main(void) {
int *pary[3];
return 0;
}
↑の例ではint
型のポインタを3つ含む配列pary
を宣言しています。
pary
には↓のようにポインタを代入することができます。
#include <stdio.h>
int main(void) {
int *pary[3];
int a = 1;
pary[0] = &a;
pary[1] = &a;
pary[2] = &a;
printf("%d\n", *pary[0]); // 1
return 0;
}
宣言した配列の要素の値はどうなっているか
宣言した配列の要素の値はどうなっているのでしょうか?
これは宣言方法によって↓の2つに分かれます。
- 値が初期化されない
- 値が初期化される
配列の宣言は文脈によって初期値が変わるので注意が必要です。
不安な場合は明示的に初期化するクセをつけたほうがいいです。
値が初期化されない場合
関数内で宣言した配列は基本的に初期化されません。
↓を見てください。
#include <stdio.h>
int main(void) {
int ary[3];
printf("%d\n", ary[0]); // 32766
printf("%d\n", ary[1]); // 0
printf("%d\n", ary[2]); // 0
return 0;
}
ary[0]
の値がデタラメな値(32766
)になっています。
これは筆者の環境の結果です。
ary[1]
とary[2]
は初期化されているように見えますが、これは偶然です。
このように関数内で宣言した配列は基本的には初期化されません。
(ただしstatic
な配列を除きます)
値が初期化される場合
グローバルな配列とstatic
な配列は初期化されます。
#include <stdio.h>
int ary1[3];
int main(void) {
static int ary2[3];
printf("ary1[0] = %d\n", ary1[0]); // ary1[0] = 0
printf("ary1[1] = %d\n", ary1[1]); // ary1[1] = 0
printf("ary1[2] = %d\n", ary1[2]); // ary1[2] = 0
printf("ary2[0] = %d\n", ary2[0]); // ary2[0] = 0
printf("ary2[1] = %d\n", ary2[1]); // ary2[1] = 0
printf("ary2[2] = %d\n", ary2[2]); // ary2[2] = 0
return 0;
}
↑の場合、ary1
はグローバルな領域で宣言された配列です。
その配列の要素の値はすべて0
に初期化されています。
ary2
はstatic
が付けられた配列ですが、これの要素の値も0
で初期化されています。
関数内で宣言された配列であってもstatic
は配列はこのように初期化されるのが特徴です。
配列の宣言の注意点
配列の宣言の注意点はなんでしょうか?
配列の宣言で気をつけたいことは?
それは↓になります。
- C言語では宣言前の配列は使えない
C言語では宣言前の配列は使えない
C言語では宣言前の配列は使うことができません。
たとえば↓のようなコードはNGです。
#include <stdio.h>
int main(void) {
printf("%d\n", ary[0]);
// GCC: error: ‘ary’ undeclared (first use in this function)
int ary[3];
return 0;
}
↑の場合、printf()
でary[0]
を参照しています。
しかしary
はprintf()
の行以前に宣言されていません。
後ろの方で宣言されています。
配列の参照では配列が前方で宣言されている必要があります。
そのため↑のコードはコンパイルエラーになります。
おわりに
今回はC言語の配列の宣言について詳しく解説しました。
配列は基本的なデータ構造でプログラミングではよく使われます。
配列の宣言方法をチェックしておくのはかなり有用だと言えます。
🦝 < 絶対配列宣言
🐭 < ……