ポインタと配列を組み合わせた入社試験問題の詳細解説

問題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 - 1aa[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 + 0x1unsigned int* 型として扱われるため、4バイト進み、0x100004 が出力されます。

タグ: ポインタ演算 配列操作 C言語 メモリレイアウト 構造体アライメント

6月23日 21:40 投稿