質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

4回答

4227閲覧

C言語 配列の初期化子が要素数より少ない場合にエラーを出したい

darudaru

総合スコア36

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/05/08 16:43

編集2020/05/09 03:01

C言語で配列の初期化子が要素数より少ない場合にエラーを出したい

C言語でプログラムを組んでいる時に疑問に思い、質問させていただきました。

配列の要素数を明示的に定義している場合において、
初期化子が要素数より多い場合はコンパイルエラーが出るのですが
初期化子が要素数より少ない場合は残りが0埋めされるだけでエラーが出ません。

例えば、ある種のデータ群を配列として持っていて
配列の中身にはそれぞれデータが必ず入っている必要がある
などといった場合に、
配列の要素数の定義だけを増加させると、中身のない領域ができてしまい危険に思いました。

どうにかしてエラーを発生させることはできないのでしょうか?
もしくはコードの書き方等で安全に設計することはできるでしょうか?

コード例

C

1//#define DATA_ARRAY_SIZE 5 → 10 に変更 2#define DATA_ARRAY_SIZE 10 3const int data_array[DATA_ARRAY_SIZE] = { 1,2,3,4,5 };

例えば、こういう場合です。

追記

具体的な状況が伝わりにくかったのでコード例を追記しました。
配列のサイズを取得したいというよりも、
あるサイズ(ここでは列挙体のサイズ)に配列のサイズを紐づけていて
そのサイズが変更された場合に何かしら気付けるような方法があるのかを知りたいです。

C

1typedef enum{ 2 a1 = 0, 3 a2, 4 a3, 5 a_NUM, //この列挙体の要素数 6}eXXX; 7 8const int data_array[a_NUM] = { 1, 2, 3 }; 9 10int main(void) 11{ 12 for (int i = 0; i < a_NUM; i++) 13 { 14 //data_array[i]を使った処理 15 } 16 17 return 0; 18}

上記のコードで、
enumに'a4'が追加された時に、
データ配列的には想定されていない'data_array[3]'にアクセスされることが危険だと感じました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/05/09 02:18

メモリが余分に確保されているだけで、特に危険は無いように思いますが、const int data_array[] = { 1,2,3,4,5 }; でコンパイルできませんか?
Zuishin

2020/05/09 03:03

もう一度書きますが、 const int data_array[] = { 1,2,3,4,5 }; でコンパイルできませんか?
darudaru

2020/05/09 03:09

ご回答ありがとうございます。 質問が説明不足だと感じ、追記させていただきました。 コンパイルは可能なのですが、余分に確保されたメモリ(適切に初期化されていない)にアクセスしてしまう可能性があることに危険を感じています。
Zuishin

2020/05/09 03:38 編集

配列のサイズが要素数によってきまるので、そのサイズ外にアクセスしなければいいだけです。 DATA_ARRAY_SIZE とそのサイズが等しいかどうか起動時に一度チェックすればより安全ですが、配列にアクセスするたびにチェックすれば更に安全です。そうしてもさほどパフォーマンスは落ちないでしょう。
thkana

2020/05/09 04:16

特に定義があるわけではないと思いますが、経験上C言語界隈では未定義の動作を引き起こすようなものを特に「危険」と呼ぶのではないでしょうか。 確かに初期化忘れもプログラムの期待しない結果を招くという意味では危険が無いわけではないですが、極論「プログラムの動作をきちんと追っていけば解釈できる」もので、それは単なるバグと呼ぶ気がします。 やりたいこととして理解はできるのですが、「危険」と言われて身構えていたら肩透かしを喰らったような、そんな感じがちょっとするんですよね。
SHOMI

2020/05/09 04:27

>初期化子が要素数より少ない場合は残りが0埋めされるだけでエラーが出ません。 コンパイラからすると、配列の要素数に満たない部分は残りは0埋めされるとプログラマが分かったうえでやっている状態なわけで。 要素数に満たない部分を0埋めされたくないのなら、要素数を指定しないでおくしかないです。
darudaru

2020/05/09 10:16

実行時エラーではなく実行前エラーを出す方法を知りたく今回質問させていただきました。 最終的にはバグであり自己責任というのは重々承知しているのですが、それに気付ける手段として何かあるのであれば知りたいと思った次第です。 皆様ご回答いただき、ありがとうございます。勉強になります。
guest

回答4

0

ベストアンサー

要素数に満たない分を0初期化されたくないのであれば、
要素数とデータを個別に管理せずにデータ数を取得すればよいのでは?

C

1const int data_array[] = { 1,2,3,4,5 }; 2const size_t DATA_ARRAY_SIZE = sizeof(data_array) / sizeof(data_array[0]);

要素数に満たない分が0(もしくはヌルポインタ)に初期化されるのはC言語の機能なので、エラーにすることはできません。
配列の初期化


(質問追記分)
コメントでthkanaさんも書かれていますが、実行時エラーとするaseertではだめですか?
注意:aseertNDEBUGマクロが定義されていると何もしないコードに置き換えられます。
gcc/g++なら明示的にNDEBUGを定義しなければaseert実行されますが、VisualC++の場合はReleaseビルド時NDEBUGが定義されます。
VisualC++のReleaseビルド時でもエラー表示したい場合はif文にでも置き換えてください。

C

1#include <assert.h> 2typedef enum { 3 a1 = 0, 4 a2, 5 a3, 6 a4, 7 a_NUM, //この列挙体の要素数 8}eXXX; 9const int data_array[] = { 1,2,3 }; 10 11int main(void) 12{ 13 assert(a_NUM == sizeof(data_array) / sizeof(data_array[0])); 14 15 for (int i = 0; i < a_NUM; i++) 16 { 17 //data_array[i]を使った処理 18 } 19 20 return 0; 21}

C言語にこだわらずC++(C++11以降)でもよいなら、以下のようにすればコンパイル時にエラーにできます。
(追記)C言語もC11以降なら_Static_assertが使用でき、assert.hをincludeすれば#define static_assert _Static_assertとdefineされるようです。手元のMinGW gcc9.2.0ではassert.hにそのような記述は含まれておらずdefineされませんでしたが、_Static_assertは使用できました。
VisualC++のC言語はC90(C89)なので_Static_assertは使用できませんが、static_assertは使えるようです。

C++

1typedef enum { 2 a1 = 0, 3 a2, 4 a3, 5 a4, 6 a_NUM, //この列挙体の要素数 7}eXXX; 8const int data_array[] = { 1,2,3 }; 9static_assert(a_NUM == sizeof(data_array) / sizeof(data_array[0]), "element count != a_NUM"); 10

投稿2020/05/08 16:56

編集2020/05/09 10:10
SHOMI

総合スコア4079

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2020/05/09 02:11

この回答のやり方でも,問題としているミスをした(=本当は要素を増やさなきゃならないのに,初期化子を少ないままにしてしまった)場合への対策にならないと思います.
thkana

2020/05/09 02:32

実行時エラーでよければ、 #define DATA_ARRAY_SIZE 10 const int data_array[] = { 1,2,3,4,5 }; assert(DATA_ARRAY_SIZE==sizeof(data_array) / sizeof(data_array[0])); とかでしょうかね。 ついでに言いたいので、勝手ながら場所をお借りして。 > 中身のない領域ができてしまい Cには「中身のない」領域はありません。0やnullは立派にひとつの値です。 「危険」とまでいうのは、型付けのないスクリプト言語とかの感覚でしょうか。
darudaru

2020/05/09 03:06

ご回答ありがとうございます。 質問が説明不足だと感じ、追記させていただきました。 「危険」というのは、実装者が想定していない(適切に初期化できていない)領域にアクセスしてしまうのが怖いという意味です。
SHOMI

2020/05/09 08:37

>この回答のやり方でも,問題としているミスをした(=本当は要素を増やさなきゃならないのに,初期化子を少ないままにしてしまった)場合への対策にならないと思います. 要素数を別途持たない場合は知るすべがないですね
darudaru

2020/05/09 10:07

ご回答ありがとうございます。 実行時エラーではなく、実行前エラーを出す方法を知りたいと思い質問しました。 _Static_assert というものがあったのですね。勉強になります。 自分の環境でもこれで実現できたので、問題を解決できました。ありがとうございます。
guest

0

初期化子が要素数より少ない場合は残りが0埋めされるだけでエラーが出ません

それを利用する技(?)もあります.

C は元々 OS を書くために発展した言語です. チップレベルの動作を理解した上でその機能を最大限に発揮できるようコードを書くための言語であり, 一見「危険」に思えるコードも, あくまでプログラマが理解・注意・意図して書いていることが大前提です.

求められるものの変化で, あるいはそのような「安全」の機能を含むコンパイラもあるかもしれませんが, そのようなモノが無くても「安全」になるように(他の方の回答にありますように)コードを書くことが技術者の腕の見せ所であり, さらには必要ならそのような機能をコンパイラを改造して作ってしまおうという気概こそ C の技術者に必要なことかと思います.

投稿2020/05/09 04:14

jimbe

総合スコア13209

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

darudaru

2020/05/09 10:02

ご回答ありがとうございます。 おっしゃられていることはごもっともです。 自分としては、例えばC#では配列要素数と初期化子の数が合っていない場合エラーが出るので C言語でも同じことを何かしらの方法で実現する方法はないのかと思い質問させていただきました。
guest

0

C11/C++11ならstatic_assertが使えるので、static_assert(sizeof(arr)/sizeof(*arr) == 5, "err");のようにできると思います。

投稿2020/05/09 04:42

yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SHOMI

2020/05/09 04:55

C11でも使えるのですね。勉強になりました。
darudaru

2020/05/09 10:03

ご回答ありがとうございます。 勉強になりました。
yumetodo

2020/05/09 15:41

static_assertはCにも入れたくせになんでconstexprはCに来ないんだ!とは友人のボヤキ。
guest

0

#define ARR_SIZE(x) (sizeof(x)/sizeof(*(x)))

というマクロを用意しとけば、これで配列のサイズを取ってこれます

#ポインタ与えるとおかしくなるので注意

投稿2020/05/09 00:04

y_waiwai

総合スコア88042

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

darudaru

2020/05/09 01:53

ご回答ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問