プロが教えるわが家の防犯対策術!

二次元配列、三次元配列のサイズが知りたいです。

unsigned char test1[100];
は sizeof( test1 )で100ですが、

unsigned char test1[100][30];
はどのように書いたら100または、30なのか知りたいです。

unsigned char test1[100][30][5];
これも100,30,5のサイズをそれぞれ得る方法が知りたいです。

因みに以下のような数値なし[]を見たことがあるのですが
どういう意味でしょうか?
unsigned char test1[][30];

サイズを理解していないのは、ポインタを理解していないと同じことでしょうか?今更ながら自分自身が不安です。

A 回答 (14件中1~10件)

> 普通は、こういうアクセスをするとメモリ破壊になるからやってはいけない



この理由と、もう一つ別の理由。

> printf("%d\n", sizeof ary / sizeof ary[100]);
> printf("%d\n", sizeof ary[100] / sizeof ary[100][30]);
> printf("%d\n", sizeof ary[100][30]) / sizeof ary[100][30][5];

は、他のサイズの配列の場合に使い回しが効かない、という弱点があります。
miraiyaの回答だと、こんな風にマクロ定義が可能です。

#include <stdio.h>

#define SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

int main(void)
{
  unsigned char test1[100];
  unsigned char test2[100][30];
  unsigned char test3[100][30][5];
  
  printf("%d\n", SIZE(test1));
  putchar('\n');
  printf("%d\n", SIZE(test2));
  printf("%d\n", SIZE(test2[0]));
  putchar('\n');
  printf("%d\n", SIZE(test3));
  printf("%d\n", SIZE(test3[0]));
  printf("%d\n", SIZE(test3[0][0]));
  return 0;
}

(注)インデントのため、全角空白を使っています。
    • good
    • 0

まず、数値無しの [] ですが、これは、多くの場合に配列名が配列の先頭を指すポインタに変換されるという規則に関わっています。


配列の定義の場合は、配列全体のサイズを決定する必要があるので、要素のサイズは省略できません。
一方で、配列の宣言(のみ)の場合(関数の仮引数や、extern と伴って使われる場合ですね)実質的には、ポインタに変換されてしまいます。

例えば、test1[10] が、 *(test1 + 10) と等価であるのはご存じかと思います。
従って、配列の宣言の時には、配列のサイズがわからなくても、処理ができるので、配列のサイズは省略できます

逆に、関数の仮引数で配列のサイズを指定しても、それによる変化はありません。例えば、
void func(char test[100])
として、この間数内部で、sizeof(test) を参照すると、ポインタのサイズになっていることがわかります。

さらにいえばC言語では、多次元配列というのは存在しません。あくまでも、「配列の配列」です。
このため、char test1[100][30][5] は、char [30][5] の配列ということで、char test1[][][5] のような省略はできません。
(ただ単に、2番目以降のサイズがわからないと、次の要素に行く時にポインタをいくつ進めたらいいかわからないというだけのことですが、それは、とりもなおさず、「どのような型の配列なのかわからない」ということでもあります)

次に、話題がずれた方向にもう一つで申し訳ありませんが……

C言語(C++でも)の規格上、ポインタというのは、必ずしも、直接の、メモリ番地を意味しません。このために、ポインタの演算結果が、必ずしもポインタとして有効であるかどうかはわかないことになっています。

このために、
unsigned char test1[100];
に対して、test1[120]; に相当するもの ( *(test1 + 120) でアクセスできるはずのメモリ)が、存在するかどうかは保障されていません。
この範囲として保障されているのが、
test[-1] から test[100] までです。
ついでにいえば、 &test[-1] < &test[0] < .. < &test[99] < &test[100] になります。かならずしも、&test[100] < &test[101] が(ポインタの計算だけだったとしても)保障されていない点に注意が必要です。

これは、malloc() などで確保した領域に対しても同様で、
char *p = malloc(100); に対して、p[100] という場所はとりあえず存在しますが、p[101] という場所は、存在すらしないかも知れないということになります。

こういう訳で、配列の、要素ひとつ前後は、具体的に要素へのアクセスを行わなければ(そのアドレスをとるだけなら)OKということになっています。
    • good
    • 0

質問者の意図からずれてしまっている様なので、話題を戻して。



>unsigned char test1[100][30];
>はどのように書いたら100または、30なのか知りたいです。

sizeof演算子は、ご存知のように渡されたオブジェクトのサイズを返すので、
sizeof test1 → 3000
sizeof test1[0] → 30
となります。
これは、test1というオブジェクトは2次元配列全体を指しますので合算になりますし、test1[0]はこの2次元配列中の先頭の配列要素(char型30個の1次元配列)を指すためです。
従って、質問に対する回答は、
sizeof test1[0] → 30
sizeof test1 / sizeof test1[0] → 100
となります。

ここで、sizeof test1[0]をsizeof test1[1]にしても結果は同じです。
(渡されたtest1[1]が2番目の配列要素になっただけでサイズは変わらないため)
因みに他の皆さんが議論されているのは、test1は0~99までの配列なので、sizeof test1[100]とした時に実際には存在しないオブジェクトと
言う観点からの議論です。(sizeof演算子は実際のメモリを参照するわけではないので、コンパイル時も実行時もエラーになることはないと思いますが。)

>unsigned char test1[100][30][5];
>これも100,30,5のサイズをそれぞれ得る方法が知りたいです。
同様に、
sizeof test1[0][0] → 5
sizeof test1[0] / sizeof test1[0][0] → 30
sizeof test1 / sizeof test1[0] → 100
が得られます。
    • good
    • 0

#7で明確に[0]にしたほうが良いですねと訂正しているのに


そのあとでもそこを突っつくのは体のいいいじめですか?

ary[100]に関してですが、宣言時も100を使っていますので
K&R の以下の記述によれば通らないことはないと思いますがどうでしょうか?

The problem is that &tab[-1] and &tab[n] are both outside the limit of the array tab.
The former is strictlly illegal, and it is illegal to dereference the latter.
The language definition does guarantee, however, that pointer arithmetic
that involves the first element beyond the end of an array
(that is, &tab[n]) will work correctly.
(6.4 Pointers to Structures)

dereference するとダメよとはありますが、今回の件に関しては
関係ないし、記述すること自体が不法ということではないと思います
(dereferece = 評価ではありません)。

ISOとかJISで違う定義になっているのならごめんなさい。
    • good
    • 0

配列の要素数を求めるための式の中に、要素数の値を含むのは


矛盾しているような気がします。

だからこそ、何人かのかたが書かれているように
「先頭要素のサイズ」で割る必要があると思います。
    • good
    • 0

「破綻する」というのはちょっと言い過ぎでありました。


「保守性が下がる」あたりの方がよかったかなと思います。

定義した範囲外の要素にアクセスするコードを書くと、
将来そのコードを保守する人(将来の自分自身を含む)が見たときに
「何かの間違いではないか???」という疑問が渦巻くことを
心配してしまいます。
    • good
    • 0

破綻するという意味では、sizeof ary[100]ではなく、%dにこそ注目すべきです。



sizeof演算子の評価結果はsize_t型であり、size_t型同士の除算結果はsize_t型またはint型になります。ここで、結果の型がint型かint型未満のサイズの型なら問題ありません(そうなる処理系はほとんどありません!)。あるいは、unsigned int型に定義されたsize_t型の場合も大抵大丈夫でしょう。しかし、size_t型の定義がunsigned long型などの場合、書式指定と実引数の型が矛盾するため、未定義の動作を引き起こします。

かといって、実引数をint型にキャストすればよいかというとそうでもありません。キャスト前の値がint型の表現範囲を超えていた場合、処理系定義の値になるか処理系定義のシグナルが発生します。結果として、期待した動作になるとは限らないわけです。(今回は15000以下の値しか扱わないので大丈夫ですが、32768以上を扱う場合は要注意です)

というわけで、こんなときはunsigned long型のような符号無し整数型で、かつ十分大きな型にキャストする方が望ましいと思います。具体的には、

printf("%lu\n", (unsigned long)(sizeof ary / sizeof ary[100]));

といった具合です。

もとの質問が処理系を特定しているのであれば、ここまで考慮する必要はないかもしれません。

ary[100]に関しては、今回はsizeof演算子のオペランドだったので問題ありません。また、&ary[100]とした場合も問題ありません。しかし、ary[100]という式を評価した場合の動作は未定義です。多くの処理系では、何事もなくコンパイルできてしまいますが、未定義の動作はエラー報告をすることを義務づけられていないので、(コンパイル時点で)既に誤動作していると考えた方が無難です。少なくとも、移植性はまったくありません。
    • good
    • 0

★私もマクロ関数を作って利用していますね。


・回答者 No.6 さんのマクロ定義と同じです。→マクロ名は違いますが…。
 #define ArrayOf(s) (sizeof(s) / sizeof((s)[0]))
 と定義して使っています。
・使い方は『unsigned char test1[100][30][5];』の場合は
 (1)ArrayOf(test1).........[100]の添え字の要素数を返す
 (2)ArrayOf(test1[0])......[30]の添え字の要素数を返す
 (3)ArrayOf(test1[0][0])...[5]の添え字の要素数を返す
・『ArrayOf()』マクロ関数はどんな型でも構造体などの配列でも添え字の要素数をコンパイル前に
 自動的に計算してくれます。定義内容から『sizeof』演算子を使っています。
 三次元配列の場合『ArrayOf(test1)』と指定すると[100]という添え字の要素数を計算しますが
 これは sizeof(test1) / sizeof(test1[0]) を計算しています。
 =sizeof(test1) / sizeof(test1[0])
 =[100]x[30]x[5] / [30]x[5]
 =15000 / 150
 =100
・多次元配列の各要素数を計算したい場合は
 ArrayOf(test1)
 ArrayOf(test1[0])
 ArrayOf(test1[0][0])
 ArrayOf(test1[0][0][0]) ←三次元配列の場合、これはエラーですよ。注意!
 などとします。
・『unsigned char test1[][30];』のような要素数を省略すると二次元の部分が『可変長』という
 感じで宣言されます。つまり『test1[1][30]』~『test1[100][30]』など二次元の添え字部分は
 固定にしないため場合によっては便利な記述です。
・例えば
 static const char *string[] = {
  "文字列1",
  "文字列2",
   :
  "文字列n",
  NULL,
 };
 という定義を見たことありませんか?
 これと同じですよ。→任意要素数を定義している。
・ただし、この指定は一番大きい外側の次元部分のみしか許されません。
 つまり、『test1[100][][30];』などはエラーです。『test1[][10][30];』としか記述できません。

最後に:
・回答者 No.1、No.3、No.7 さんのように添え字の数に『[0]』以外を指定しても正しくコンパイル
 できます。また、動作にも悪影響はありません。でも『sizeof(ary[100][30][5]);』という指定方法は
 誤解を招きやすいと思います。『破綻する』か、『破綻しない』とかの問題ではありません。
 可読性です。ぱっと見て『間違い?』と見えるような記述はしない方が良いと言うことです。
・以上。おわり。
    • good
    • 0

#1です。


徹夜寸前の状況で書いたんで不備があることは認めますが、
考えなしに100だの30だのの生の数字を書いたわけではありません。
[0]を使ったほうが良いことはその通りですし、議論を巻き起こすことは
本意でないしここではご法度ですので詳細は書きません。

ですが、

> 定義外の添字を使っているために破綻します。

破綻とはどのような意図で書かれていますか?
正しい答えが求まらないとか、コンパイルエラーになるとかいうことでしょうか。
もしそのようなコンパイラがあるというなら教えていただけませんか?

一応コンパイルして結果を確かめてから書いていますから。
もちろんあるコンパイラで通るからといってそれが問題ないという
ことではないということはわかりますが、「破綻する」とまで云われては
文句のひとつもつけたくなります。
    • good
    • 0

配列の要素数は「(配列全体のサイズ)/(1要素のサイズ)」ですから、


「sizeof(a)/sizeof(a[0])」でも「sizeof(a)/sizeof(a[1])」でも
はたまた「sizeof(a)/sizeof(a[100])」でも計算できるのは確かです。
sizeof 自体はコンパイル時に解決されますから実行時にエラーになる
ということもありませんし。

ただし「sizeof(a)/sizeof(a[100])」などと書こうものなら、後で
そのコードを読んだ人を混乱におとしいれること間違いなしです
(書いた人自身も含めて)。
定石どおり素直に「sizeof(a)/sizeof(a[0])」と書きましょう。
多次元配列の場合はNo.2の方が示されているとおりに。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!