広告スペース

メモリトレース問題集

コードを読んで、各変数の値がどう変化するかを追跡する練習問題です。紙とペンで解いてみましょう。

変数・代入のトレース

T-01変数
int a = 5, b = 10;
a = b;
b = a + 3;
a = b - a;
実行後の a, b の値は?
1行目: a=5, b=10
2行目: a=10, b=10(aにbの値を代入)
3行目: a=10, b=13(10+3)
4行目: a=3, b=13(13-10)
答え: a=3, b=13
T-02変数
int x = 7;
x = x * 2;
x += 3;
x %= 5;
x++;
最終的な x の値は?
x=7 → x=14(7*2) → x=17(14+3) → x=2(17%5=余り2) → x=3(2+1)
答え: x=3
T-03変数
int a = 3, b = 5, tmp;
tmp = a;
a = b;
b = tmp;
実行後の a, b, tmp の値は?(これは何をしている?)
tmp=3(aの元の値を退避) → a=5(bの値をaに) → b=3(tmpからaの元の値をbに)
答え: a=5, b=3, tmp=3
これは値の交換(swap)の典型パターンです。
T-04変数
int a = 10;
int b = a / 3;
int c = a % 3;
double d = (double)a / 3;
b, c, d の値は?
b = 10/3 = 3(整数除算で切り捨て)
c = 10%3 = 1(余り)
d = 10.0/3 = 3.333...(キャストでdouble除算に)
答え: b=3, c=1, d=3.333333
T-05変数
int a = 1;
a += a++;  // 未定義動作に注意
printf("%d\n", a);
出力は何? ヒント:これは「罠」問題です。
未定義動作です。同じ変数を1つの式の中で変更と参照を同時に行うと、C言語の仕様上結果は保証されません。
コンパイラによって 2 や 3 など異なる結果が出ます。
教訓: ++ と代入を同じ式で混ぜない。
T-06変数
int i = 0, sum = 0;
while (i < 5) {
    sum += i;
    i++;
}
各ループ後の i, sum を表で追跡せよ。
i(加算前)sum += ii++後
100+0=01
210+1=12
321+2=33
433+3=64
546+4=105
答え: i=5, sum=10(0+1+2+3+4)
T-07変数
int n = 12345, rev = 0;
while (n > 0) {
    rev = rev * 10 + n % 10;
    n /= 10;
}
各ループ後の n, rev を追跡せよ。このプログラムは何をしている?
n%10revn
150*10+5=51234
245*10+4=54123
3354*10+3=54312
42543*10+2=54321
515432*10+1=543210
答え: n=0, rev=54321
これは整数の桁を逆順にするプログラムです。

配列のトレース

T-08配列
int a[5] = {3, 1, 4, 1, 5};
a[0] = a[4];
a[2] = a[0] + a[1];
a[4] = a[2] - a[3];
配列 a の最終状態は?
初期: {3,1,4,1,5}
a[0]=a[4] → {5,1,4,1,5}
a[2]=a[0]+a[1] → {5,1,6,1,5}
a[4]=a[2]-a[3] → {5,1,6,1,5}
答え: {5, 1, 6, 1, 5}
T-09配列
int a[4] = {10, 20, 30, 40};
for (int i = 0; i < 3; i++) {
    a[i] = a[i + 1];
}
配列 a の最終状態は? これは何をしている?
i=0: a[0]=a[1] → {20,20,30,40}
i=1: a[1]=a[2] → {20,30,30,40}
i=2: a[2]=a[3] → {20,30,40,40}
答え: {20, 30, 40, 40}
先頭要素を削除して左にシフトする処理(末尾が残る)。
T-10配列
int a[5] = {2, 8, 3, 9, 1};
int max = a[0], idx = 0;
for (int i = 1; i < 5; i++) {
    if (a[i] > max) {
        max = a[i];
        idx = i;
    }
}
各ステップで max, idx がどう変わるか追跡せよ。
ia[i]a[i]>max?maxidx
初期--20
188>2 Yes81
233>8 No81
399>8 Yes93
411>9 No93
答え: max=9, idx=3
T-11配列
int a[5] = {5, 3, 8, 1, 4};
// バブルソート1回目(最小値を先頭に)
for (int j = 4; j > 0; j--) {
    if (a[j-1] > a[j]) {
        int tmp = a[j-1]; a[j-1] = a[j]; a[j] = tmp;
    }
}
1パス後の配列の状態を追跡せよ。
初期: {5,3,8,1,4}
j=4: a[3]>a[4]? 1>4 No → {5,3,8,1,4}
j=3: a[2]>a[3]? 8>1 Yes → {5,3,1,8,4}
j=2: a[1]>a[2]? 3>1 Yes → {5,1,3,8,4}
j=1: a[0]>a[1]? 5>1 Yes → {1,5,3,8,4}
答え: {1, 5, 3, 8, 4}(最小値1が先頭に来る)
T-12配列
int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int sum = 0;
for (int i = 0; i < 3; i++) {
    sum += a[i][i];
}
sum の値は? この処理は何を計算している?
a[0][0]=1, a[1][1]=5, a[2][2]=9
sum = 1+5+9 = 15
これは行列の対角線上の要素の合計(トレース)です。

ポインタのトレース

T-13ポインタ
int a = 10, b = 20;
int *p = &a;
*p = 30;
p = &b;
*p = *p + 5;
実行後の a, b, *p の値は?
p=&a → *p=30 → a=30
p=&b → *p=20+5=25 → b=25
答え: a=30, b=25, *p=25
T-14ポインタ
int a = 5, b = 10;
int *p = &a, *q = &b;
*p = *q;
*q = 0;
printf("%d %d\n", a, b);
出力は?
*p=*q → a=10(bの値をaに)
*q=0 → b=0
答え: 10 0
T-15ポインタ
int arr[4] = {10, 20, 30, 40};
int *p = arr;
printf("%d ", *p);
p += 2;
printf("%d ", *p);
printf("%d ", *(p - 1));
printf("%d\n", p[1]);
出力は?
p=arr → *p=10
p+=2 → pはarr[2]を指す → *p=30
*(p-1)=arr[1]=20
p[1]=*(p+1)=arr[3]=40
答え: 10 30 20 40
T-16ポインタ
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr + 1;
int *q = arr + 3;
printf("%d %d %ld\n", *p, *q, q - p);
出力は?
p=&arr[1] → *p=2
q=&arr[3] → *q=4
q-p = 3-1 = 2(要素数の差)
答え: 2 4 2
T-17ポインタ
int x = 100;
int *p = &x;
int **pp = &p;
**pp = 200;
printf("%d %d %d\n", x, *p, **pp);
出力は?(ダブルポインタ)
pp→p→x の連鎖。**pp=200 は x を200に変更。
x=200, *p=200, **pp=200(全部同じ場所を指す)
答え: 200 200 200
T-18ポインタ
int a[] = {10, 20, 30, 40, 50};
int *p = a;
for (int i = 0; i < 5; i++) {
    *(p + i) *= 2;
}
配列 a の最終状態は?
各要素を2倍: {20, 40, 60, 80, 100}
*(p+i) は a[i] と同じ。ポインタ演算で配列を操作。

関数呼び出しのトレース

T-19関数
void add_ten(int x) { x += 10; }

int main(void) {
    int a = 5;
    add_ten(a);
    printf("%d\n", a);
}
出力は?
答え: 5
Cは値渡し。add_tenのxはaのコピーなので、xを変えてもaには影響しない。
T-20関数
void add_ten(int *px) { *px += 10; }

int main(void) {
    int a = 5;
    add_ten(&a);
    printf("%d\n", a);
}
出力は?(T-19との違いに注目)
答え: 15
ポインタ経由で渡しているので、*px += 10 は a 自体を変更する。
T-21関数
int mystery(int a, int b) {
    a = a + b;
    b = a - b;
    a = a - b;
    return a * 10 + b;
}
printf("%d\n", mystery(3, 7));
出力は?(関数内の a, b を1行ずつ追跡)
a=3, b=7
a=3+7=10
b=10-7=3
a=10-3=7
return 7*10+3 = 73
これは一時変数なしのswap。a,bが交換されて 7*10+3=73。
T-22関数
void modify(int arr[], int n) {
    for (int i = 0; i < n; i++) arr[i] *= 3;
}
int main(void) {
    int data[] = {1, 2, 3};
    modify(data, 3);
    printf("%d %d %d\n", data[0], data[1], data[2]);
}
出力は?(配列の値渡しとポインタ渡しの違い)
答え: 3 6 9
配列を関数に渡すとポインタとして渡されるので、関数内の変更は呼び出し元に反映される。intの値渡し(T-19)とは違う!
T-23関数
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
printf("%d\n", factorial(5));
コールスタックの展開と戻りを追跡せよ。
factorial(5) → 5 * factorial(4)
→ 5 * 4 * factorial(3)
→ 5 * 4 * 3 * factorial(2)
→ 5 * 4 * 3 * 2 * factorial(1)
→ 5 * 4 * 3 * 2 * 1(ベースケース)
= 5*4*3*2*1 = 120

文字列のトレース

T-24文字列
char s[] = "Hello";
s[0] = 'J';
s[4] = '!';
printf("%s\n", s);
出力は? メモリ上の各バイトは?
index012345
初期Hello\0
変更後Jell!\0
答え: Jell!
T-25文字列
char s[] = "abcde";
int len = 0;
while (s[len] != '\0') len++;
printf("%d\n", len);
lenの変化を追跡し、出力を答えよ。
s[0]='a'≠'\0' → len=1
s[1]='b'≠'\0' → len=2
s[2]='c'≠'\0' → len=3
s[3]='d'≠'\0' → len=4
s[4]='e'≠'\0' → len=5
s[5]='\0' → ループ終了
答え: 5(strlenと同じ処理)
T-26文字列
char s[] = "Hello";
for (int i = 0; s[i]; i++) {
    if (s[i] >= 'a' && s[i] <= 'z')
        s[i] -= 32;
}
printf("%s\n", s);
各文字の変化を追跡せよ。
H: 大文字→変更なし
e: 小文字→e-32=E
l: 小文字→l-32=L
l: 小文字→l-32=L
o: 小文字→o-32=O
答え: HELLO(小文字→大文字変換。ASCIIコードで差は32)

総合問題

T-27総合
struct Point { int x, y; };
struct Point a = {3, 7};
struct Point b = a;
b.x = 10;
printf("a=(%d,%d) b=(%d,%d)\n", a.x, a.y, b.x, b.y);
出力は?(構造体の代入は値コピー?参照?)
答え: a=(3,7) b=(10,7)
構造体の代入は値コピー。bはaの独立したコピーなので、b.xを変えてもa.xは変わらない。
T-28総合
struct Point { int x, y; };
struct Point p = {1, 2};
struct Point *ptr = &p;
ptr->x = 10;
ptr->y = ptr->x + 5;
printf("(%d,%d)\n", p.x, p.y);
出力は?(アロー演算子経由の変更)
ptr->x=10 → p.x=10
ptr->y=10+5=15 → p.y=15
答え: (10,15)
T-29総合
int a[3] = {1, 2, 3};
int *p = a;
for (int i = 0; i < 3; i++) {
    a[i] = a[(2-i)];
}
配列aの最終状態は?(これは「逆順」にならない。なぜ?)
i=0: a[0]=a[2] → {3,2,3}
i=1: a[1]=a[1] → {3,2,3}
i=2: a[2]=a[0] → {3,2,3}(a[0]は既に3に変わっている!)
答え: {3, 2, 3}({3,2,1}にはならない)
理由: 前半の要素を上書きした後に、その値を使ってしまうため。正しい逆順には一時配列が必要。
T-30総合
int f(int *a, int *b) {
    *a += *b;
    *b = *a - *b;
    return *a + *b;
}
int main(void) {
    int x = 4, y = 6;
    int z = f(&x, &y);
    printf("%d %d %d\n", x, y, z);
}
出力は?(ポインタ経由での変更を追跡)
*a=&x, *b=&y
*a += *b → x=4+6=10
*b = *a-*b → y=10-6=4
return 10+4=14
答え: 10 4 14
T-31総合
int a[5] = {5, 2, 8, 1, 9};
for (int i = 0; i < 4; i++) {
    if (a[i] > a[i+1]) {
        int t = a[i]; a[i] = a[i+1]; a[i+1] = t;
    }
}
配列aの最終状態は? 何が保証される?
i=0: 5>2 → swap → {2,5,8,1,9}
i=1: 5>8? No → {2,5,8,1,9}
i=2: 8>1 → swap → {2,5,1,8,9}
i=3: 8>9? No → {2,5,1,8,9}
答え: {2, 5, 1, 8, 9}
最大値(9)が末尾に来ることが保証される(バブルソート1パス・前方走査版)。
T-32総合
int count = 0;
for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0 && i % 5 == 0)
        count++;
}
countの最終値は?(全部追跡しなくてOK。規則を見つけよ。)
3と5の両方で割り切れる → 15の倍数をカウント。
1〜100の15の倍数: 15,30,45,60,75,90 の6個
答え: count=6
T-33総合
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}
printf("%d\n", fib(6));
fib(6)の値と、fib関数が合計何回呼ばれるか答えよ。
fib(6)=fib(5)+fib(4)=5+3=8
呼び出し回数: fib(6)→fib(5),fib(4)→...と展開していくと25回
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5, fib(6)=8
T-34総合
char *p = "ABCDE";
printf("%c %c %s\n", *p, *(p+2), p+3);
出力は?(文字列リテラルのポインタ操作)
*p = 'A'
*(p+2) = 'C'
p+3 = "DE"(p+3以降の文字列)
答え: A C DE
T-35総合
int m[2][3] = {{1,2,3},{4,5,6}};
int result = 0;
for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        if (m[i][j] % 2 == 0) result += m[i][j];
resultの値は?
偶数の要素: 2, 4, 6
result = 2+4+6 = 12
T-36総合
int a = 0xAB;  // 16進数
int hi = (a >> 4) & 0x0F;
int lo = a & 0x0F;
printf("hi=%d lo=%d\n", hi, lo);
出力は?(ビット演算のトレース)
0xAB = 1010 1011 (2進)
a>>4 = 0000 1010 → & 0x0F → 10 (0x0A)
a & 0x0F = 0000 1011 → 11 (0x0B)
答え: hi=10 lo=11(上位4bit=A=10, 下位4bit=B=11)
T-37総合
int gcd(int a, int b) {
    while (b != 0) {
        int tmp = b;
        b = a % b;
        a = tmp;
    }
    return a;
}
printf("%d\n", gcd(48, 18));
各ループの a, b, tmp を追跡せよ。
abtmpa%b
148181848%18=12
218121218%12=6
3126612%6=0
終了60--
答え: 6(48と18の最大公約数)
T-38総合
int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;
int *q = p;
free(p);
// printf("%d\n", *q); ← これは安全?
free後に *q にアクセスするとどうなる?
未定義動作(ダングリングポインタ)
qはpと同じ場所を指していたが、free(p)で解放済み。qはまだ同じアドレスを指しているが、その領域はもう使えない。
教訓: free後は関連するポインタも全てNULLにする。
T-39総合
int a[4] = {10, 20, 30, 40};
int *p = a, *q = a + 3;
while (p < q) {
    int t = *p; *p = *q; *q = t;
    p++; q--;
}
配列aの最終状態は? 何をしている?
1回目: p=a[0],q=a[3] → swap(10,40) → {40,20,30,10}, p++,q--
2回目: p=a[1],q=a[2] → swap(20,30) → {40,30,20,10}, p++,q--
p>=q → 終了
答え: {40, 30, 20, 10}
これは配列の逆順(in-place reverse)。両端から中心に向かってswapする。
T-40総合
int binary_search(int a[], int n, int key) {
    int lo = 0, hi = n - 1;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;
        if (a[mid] == key) return mid;
        else if (a[mid] < key) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}
// a = {2, 5, 8, 12, 16, 23, 38, 56}, key = 23
lo, hi, mid の変化を追跡し、何回のループで見つかるか答えよ。
lohimida[mid]判定
10731212<23 → lo=4
24752323==23 → 発見!
答え: 2回目で index=5 を返す

トレース問題をもっと練習するなら

紙とペンで追跡する練習は、試験対策に最も効果的です

📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。丁寧な解説で基礎を固められます。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で、演習問題も充実。大学の教科書としても採用多数。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。基礎を終えた後のステップアップに最適。
Amazonで見る

※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。

この記事をシェア
X(Twitter)でシェア Facebookでシェア LINEで送る はてブ