問題1:ポインタ演算とメモリ配置の理解
#include <stdio.h>
int main() {
int a[4] = {1, 2, 3, 4};
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x %x\n", ptr1[-1], *ptr2);
return 0;
}
&a は配列全体のアドレスを指し、その型は int (*)[4] です。
&a + 1 は配列全体を飛び越えたアドレスを示します。これを int* にキャストして ptr1 に代入しています。
ptr1[-1] は *(ptr1 - 1) と同じで、配列の最後の要素である 4 を指します。
一方、(int)a + 1 はアドレス値を整数として扱い、1バイトだけインクリメントします。
この結果、ptr2 は先頭アドレスから1バイトずれた位置を指します。
int 型のポインタが4バイトを解釈するため、小端方式(little-endian)ではバイト列 00 00 00 02 が逆順に読み取られ、20000000 という16進数が出力されます。
問題2:多次元配列と初期化の挙動
int a[3][2] = { (0,1), (2,3), (4,5) };
int* p = a[0];
printf("%d\n", p[0]);
初期化子内の括弧が波ではなく丸であることから、これはコンマ演算子として評価されます。
コンマ演算子は左側を無視し、右側の値を採用するため、実際には以下のように初期化されます。
a[3][2] = { 1, 3, 5 }; // 残りは0で埋められる
// 完全な配列は:
// { {1, 3}, {5, 0}, {0, 0} }
p = a[0] より、p は1行目の先頭アドレスを保持します。
したがって p[0] は a[0][0]、すなわち 1 を出力します。
問題3:異なる型のポインタ間での算術演算
int a[5][5];
int (*p)[4];
p = (int(*)[4])a;
printf("%p, %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
a の型は int(*)[5] であり、a + 1 は5つの int 分(=20バイト)進みます。
p の型は int(*)[4] なので、p + 1 は4つの int 分(=16バイト)進みます。
p[4] は *(p + 4) に等しく、4 * 16 = 64 バイト進んだ位置を指します。
a[4] は 4 * 20 = 80 バイト進んだ位置を指します。
しかし、差分計算においてはポインタの型によって単位が決まります。
両方とも int* の差として評価されるため、結果は相対的なint 要素数の差となります。
具体的な値は実行環境によりますが、ポインタ減算の結果は符号付き整数として得られます。
問題4:2次元配列のアドレス操作
int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d, %d\n", *(ptr1 - 1), *(ptr2 - 1));
&aa + 1 は2次元配列全体(2×5=10要素)を飛び越えます。
ptr1 はその先頭を int* として指しているため、ptr1 - 1 は配列の最終要素、すなわち 10 を指します。
aa + 1 は最初の行(5要素)を飛び越え、2行目の先頭 &aa[1][0] を指します。
*(aa + 1) はそのアドレス値(int*)であり、ptr2 はそれを保持します。
ptr2 - 1 は aa[0][4]、つまり 5 を指すことになります。
出力は 10, 5 です。
問題5:文字列ポインタ配列の操作
char* a[] = {"work", "at", "alibaba"};
char** pa = a;
pa++;
printf("%s\n", *pa);
a は文字列リテラルへのポインタを格納する配列です。
pa = a より、pa は配列の先頭("work" のアドレス)を指します。
pa++ は、pa の型が char** であるため、1つの char* 分(通常4または8バイト)進みます。
これにより、"at" のアドレスを指すようになります。
*pa はそのアドレスの内容、すなわち文字列 "at" を取得し、%s で出力されます。
問題6:構造体ポインタとアドレス計算
struct stu {
int num;
char* name;
short data;
char c[2];
short sex[4];
} *p = (struct stu*)0x100000;
printf("p+0x1: %p\n", p + 0x1);
printf("(unsigned long)p + 0x1: %p\n", (unsigned long)p + 0x1);
printf("(unsigned int*)p + 0x1: %p\n", (unsigned int*)p + 0x1);
まず、sizeof(struct stu) を確認します。
int num: 4バイトchar* name: 8バイト(64ビット環境)short data: 2バイトchar c[2]: 2バイトshort sex[4]: 8バイト
合計で24バイトですが、アライメントの影響で20~24バイトになる可能性があります(環境依存)。ここでは20バイトと仮定します。
p + 0x1 は構造体1個分(20バイト)進むため、0x100014 が出力されます。
(unsigned long)p + 0x1 は純粋な数値加算なので、0x100001 になります。
(unsigned int*)p + 0x1 は unsigned int* 型として扱われるため、4バイト進み、0x100004 が出力されます。