配列について
Re: 配列について
m[0][2]の後に続けてm[1][0], m[1][1], ... と配置する実装であり、たまたま初期化した場所を読み込んだからでしょう。
確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
N1570 J.2 Undefined behaviorより引用
確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
N1570 J.2 Undefined behaviorより引用
1 The behavior is undefined in the following circumstances:
(中略)
- An array subscript is out of range, even if an object is apparently accessible with the
given subscript (as in the lvalue expression a[1][7] given the declaration int
a[4][5]) (6.5.6).
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: 配列について
#include <stdio.h>
int main()
{
int a[3][3], k = 0, m = -3, n = -6;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
a[i][j] = 900 + i * 10 + j;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
printf(" a[%d][%d]:%d a[0][%d]:%d a[1][%2d]:%d a[2][%2d]:%d\n",
i, j, a[i][j], k, a[0][k], m, a[1][m], n, a[2][n]);
k++, m++, n++;
}
return 0;
}
a[0][0]:900 a[0][0]:900 a[1][-3]:900 a[2][-6]:900
a[0][1]:901 a[0][1]:901 a[1][-2]:901 a[2][-5]:901
a[0][2]:902 a[0][2]:902 a[1][-1]:902 a[2][-4]:902
a[1][0]:910 a[0][3]:910 a[1][ 0]:910 a[2][-3]:910
a[1][1]:911 a[0][4]:911 a[1][ 1]:911 a[2][-2]:911
a[1][2]:912 a[0][5]:912 a[1][ 2]:912 a[2][-1]:912
a[2][0]:920 a[0][6]:920 a[1][ 3]:920 a[2][ 0]:920
a[2][1]:921 a[0][7]:921 a[1][ 4]:921 a[2][ 1]:921
a[2][2]:922 a[0][8]:922 a[1][ 5]:922 a[2][ 2]:922
ならない処理系があったら、教えてほしい。
Re: 配列について
横からの質問になってしまい、申し訳ありません。みけCAT さんが書きました:m[0][2]の後に続けてm[1][0], m[1][1], ... と配置する実装であり、たまたま初期化した場所を読み込んだからでしょう。
確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
N1570 J.2 Undefined behaviorより引用1 The behavior is undefined in the following circumstances:
(中略)
- An array subscript is out of range, even if an object is apparently accessible with the
given subscript (as in the lvalue expression a[1][7] given the declaration int
a[4][5]) (6.5.6).
二次元配列として確保した場合、連続する領域に確保される事は保証された動作だと思っていたのですが、そうならない場合はあるのでしょうか?
Re: 配列について
>C言語の2次元配列について質問です。
>以下のmainでは初期化をm[0][0]からm[2][2]まで行っています。
>しかし、初期化を行っていないはずのm[0][3]、m[1][4]にも-1が入っているようです。
>何が原因でしょうか?
C言語は”プログラマーは全知全能である”という理念のもとに設計されています。
UNIXはアセンブラーで開発されていましたがUNIXの作者Ken Tompsonはアセンブラーが嫌になって”B"という言語を開発しました。Bは遅すぎたため使われませんでした。1971年Tompsonの同僚Dennis Ritchie は"NB"(New B)を作りました。これがのち”C"と呼ばれるようになりました。
Cはその後UNIXのニーズに合わせかなり行き当たりばったりに機能拡張を繰り返して来ました。
・現場の人間が目前の問題を解決するために作成した言語であり
・非常に便利ではあるけれども
・見た目あちこち不格好で
・よくわかっていない人がうっかり使うと危険な目に合ったりする
つまり”自分の必要に応じて作成したので”実用性は高いが人間工学には問題がありです。コンパイラの警告レベルはなるべく上げることです。
>確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
このように、こういう事は論外でやってはいけません。
参考
2次元の配列の初期化
[/size]
>以下のmainでは初期化をm[0][0]からm[2][2]まで行っています。
>しかし、初期化を行っていないはずのm[0][3]、m[1][4]にも-1が入っているようです。
>何が原因でしょうか?
C言語は”プログラマーは全知全能である”という理念のもとに設計されています。
UNIXはアセンブラーで開発されていましたがUNIXの作者Ken Tompsonはアセンブラーが嫌になって”B"という言語を開発しました。Bは遅すぎたため使われませんでした。1971年Tompsonの同僚Dennis Ritchie は"NB"(New B)を作りました。これがのち”C"と呼ばれるようになりました。
Cはその後UNIXのニーズに合わせかなり行き当たりばったりに機能拡張を繰り返して来ました。
・現場の人間が目前の問題を解決するために作成した言語であり
・非常に便利ではあるけれども
・見た目あちこち不格好で
・よくわかっていない人がうっかり使うと危険な目に合ったりする
つまり”自分の必要に応じて作成したので”実用性は高いが人間工学には問題がありです。コンパイラの警告レベルはなるべく上げることです。
>確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
このように、こういう事は論外でやってはいけません。
参考
2次元の配列の初期化
#include <stdio.h>
int main() {
int m[][3] = {
{ -1,-2,-3 },
{ -4,-5,-6 },
{ -7,-8,-9 },
};
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
printf("%d\n", m[i][j]);
}
printf("\n");
}
return 0;
}
1>------ すべてのリビルド開始: プロジェクト:ConsoleApplication6, 構成: Debug Win32 ------
1>c1.c
1>ConsoleApplication6.vcxproj -> D:\z17\c\ConsoleApplication6\Debug\ConsoleApplication6.exe
1>ConsoleApplication6.vcxproj -> D:\z17\c\ConsoleApplication6\Debug\ConsoleApplication6.pdb (Partial PDB)
========== すべてリビルド: 1 正常終了、0 失敗、0 スキップ ==========
Re: 配列について
>やってはいけないと言われても、やれば通常はこうなります。
>ならない処理系があったら、教えてほしい。
[]はシンタックスシュガーなので a[1][-3]:900 a[2][-6]:900 は a[0][0]:900 であり 正しい使い方ですよ。
http://dixq.net/forum/viewtopic.php?f=3&t=19227参照
Re: 配列について
>横からの質問になってしまい、申し訳ありません。
>二次元配列として確保した場合、連続する領域に確保される事は保証された動作だと思っていたのですが、
>そうならない場合はあるのでしょうか?
Cの規格は言語仕様を規定するものであり実装方法について規定するものではありません。
いまのPCのOSなら仮想アドレスを実現しているとおもいますがそのうえで普通はそうなるでしょう(^^;
>二次元配列として確保した場合、連続する領域に確保される事は保証された動作だと思っていたのですが、
>そうならない場合はあるのでしょうか?
Cの規格は言語仕様を規定するものであり実装方法について規定するものではありません。
いまのPCのOSなら仮想アドレスを実現しているとおもいますがそのうえで普通はそうなるでしょう(^^;
Re: 配列について
回答ありがとうございます。
つまり、m[0][0]からm[2][2]まで初期化しましたが、範囲外もたまたま初期化されたということでしょうか?ミケCAT さんが書きました:m[0][2]の後に続けてm[1][0], m[1][1], ... と配置する実装であり、たまたま初期化した場所を読み込んだからでしょう。
確保した配列の範囲外の読み書きをすると未定義動作となり、何が起こってもおかしくなくなるので、やってはいけません。
Re: 配列について
#include <stdio.h>
int main(void)
{
int m[3][3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
m[i][j] = 900 + i * 10 + j;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
printf(" %p: m[%d][%d]=%d\n", &m[i][j], i, j, m[i][j]);
printf("\n");
printf(" %p: m[0][3]=%d\n", &m[0][3], m[0][3]);
printf(" %p: m[1][3]=%d\n", &m[1][3], m[1][3]);
printf(" %p: m[1][4]=%d\n", &m[1][4], m[1][4]);
return 0;
}
00FFF984: m[0][0]=900
00FFF988: m[0][1]=901
00FFF98C: m[0][2]=902
00FFF990: m[1][0]=910
00FFF994: m[1][1]=911
00FFF998: m[1][2]=912
00FFF99C: m[2][0]=920
00FFF9A0: m[2][1]=921
00FFF9A4: m[2][2]=922
00FFF990: m[0][3]=910
00FFF99C: m[1][3]=920
00FFF9A0: m[1][4]=921
m[1][3] は m[2][0] と同じ場所だということ。
m[1][4] は m[2][1] と同じ場所だということ。
m[0][-1] や m[2][3] ならメモリが割り付けれていないから、
他の変数を壊したり、関数からのリターンアドレスなどを
壊したりして、わけの分からない動作をするということです。
Re: 配列について
C99より前は、構造体の最後にサイズ1の配列を配置して、バイナリのヘッダ構造に続くデータを読み書きする方法を慣例として認める記述がありましたが、規格としては特に規定はありませんでした。
C99では、構造体の最後のメンバに限り、サイズ指定のない配列の宣言が規格として取り入れられました。
それに合わせて、配列に対しては、厳格となりました。
みけCATさんの指摘は、よく見てもらったら分かると思いますが、6.5.6に対する注釈であり、aに対しては未定義動作となるという記述ですが、pに対しては言及されていません。
C99では、構造体の最後のメンバに限り、サイズ指定のない配列の宣言が規格として取り入れられました。
それに合わせて、配列に対しては、厳格となりました。
みけCATさんの指摘は、よく見てもらったら分かると思いますが、6.5.6に対する注釈であり、aに対しては未定義動作となるという記述ですが、pに対しては言及されていません。
Re: 配列について
オフトピック
No.1のコードは下記のようにするとC99でも(言及がないという意味で)許容範囲になるのかな。
配列名を使って配列にアクセスする部分に対して、未定義の動作とするのは、処理系が負う責任の中身を明確にする意味があるのではないだろうか。
Re: 配列について
”式の中では配列はポインターに読み替えられる”ので
void f( int hoge[3][2] );とか
void f( int hoge[][2] ); は
void f( int (*hoge)[2] ); のシンタックスシュガーでありまったく同じ意味になります。
[参考]
(配列に関する演算子:
後置演算子[]は添字演算子と呼び ポインターと整数をオペランドとして取ります。
p は *(p + i) のシンタックスシュガーでありそれ以外の意味はありません。)
とのことです。
void f( int hoge[3][2] );とか
void f( int hoge[][2] ); は
void f( int (*hoge)[2] ); のシンタックスシュガーでありまったく同じ意味になります。
[参考]
(配列に関する演算子:
後置演算子[]は添字演算子と呼び ポインターと整数をオペランドとして取ります。
p は *(p + i) のシンタックスシュガーでありそれ以外の意味はありません。)
とのことです。
Re: 配列について
Cには実際には多次元配列は存在しないのであって多次元配列のように見えるものは「配列の配列」です。
型Tの配列を引数としてわたすには「Tヘのポインター」を渡せば良いので
「配列の配列」を引数としてわたすには「配列ヘのポインター」を渡せということになります。
[/size]
型Tの配列を引数としてわたすには「Tヘのポインター」を渡せば良いので
「配列の配列」を引数としてわたすには「配列ヘのポインター」を渡せということになります。
#include <stdio.h>
void f(int(*m)[3])
{
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
printf("%d\n", m[i][j]);
}
printf("\n");
}
}
int main() {
int m[][3] = {
{ -1,-2,-3 },
{ -4,-5,-6 },
{ -7,-8,-9 },
};
f(m);
return 0;
}
1>------ すべてのリビルド開始: プロジェクト:ConsoleApplication9, 構成: Debug Win32 ------
1>c1.c
1>ConsoleApplication9.vcxproj -> D:\z17\c\ConsoleApplication9\Debug\ConsoleApplication9.exe
1>ConsoleApplication9.vcxproj -> D:\z17\c\ConsoleApplication9\Debug\ConsoleApplication9.pdb (Partial PDB)
========== すべてリビルド: 1 正常終了、0 失敗、0 スキップ ==========
[code]
-1
-2
-3
-4
-5
-6
-7
-8
-9
続行するには何かキーを押してください . . .