[C言語] Cパズルブック 2.1.1 文字型, 文字列型, 整数型

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

難しかった第6章が終わり、前に戻って第2章から始めます。

問2.1.1のコードと出力は以下の通りです。

整数5は文字列としてはASCIIコード35(16進数, 10進数では53)になります。

LLDBデバッガで確認したところ、最初の2変数はスタック領域に整数やASCIIコードとして格納されていますが、stringはポインタなのでメモリアドレスが格納されていました。文字列”5″はスタック領域の前の方に格納されていました。

#include "stdio.h"
#define PRINT(format, x) printf("x = %"#format"\n",x)

int integer = 5;
char character = '5';
char *string = "5";

int main() {
        PRINT(d, string);
        PRINT(x, string); // 追加
        PRINT(x, &string); // 追加
        printf("\n");

        PRINT(d, character);
        PRINT(d, integer);
        PRINT(s, string);
        PRINT(c, character);
        PRINT(c, integer=53);
        PRINT(d, ('5' > 5));

    return 0;
}
x = 10420060
x = 9eff5c
x = 9f4008

x = 53
x = 5
x = 5
x = 5
x = 5
x = 1 // 53(10進数) > 5はTRUE

[C言語] Cパズルブック 6.4 ポインタによる混乱

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.4のコードと出力は以下の通りです。

インクリメント++やデクリメント–では配列の基準点が移動しますが、添字では移動せず読み取りだけであるというところがポイントです。教本P151以降に詳しい解説があります。

ポインタの特徴を生かしつつ極力ややこしくした問題です。アスタリスクを3つも並べるに至っては狂気の沙汰です。このような問題でポインタ嫌いになりC言語から離れていった方が量産されたのではないでしょうか。

ただ当時のメモリやストレージの貧弱さを考えるとこのような手法はおふざけではなく真面目に使われることもあったのでしょう。

#include "defs.h"

char *c[] = {
        "ENTER",
        "NEW",
        "POINT",
        "FIRST"
        };

char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

int main() {
        printf("%s\n", **++cpp);
        // *(*(++CPP))
        // 1.++CPP
        // cppで配列cpの2番要素c+2へ移る
        // 2.*(++CPP)
        // 配列cのc+2へ移る
        // 3.*(*(++CPP))
        // 参照先の格納文字列"POINT"を指す

        printf("%s\n", *--*++cpp + 3);
        // *(--(*(++cpp)))
        // 1.++cpp
        // cppで3番要素c+1へ移る
        // 2.*(++cpp)
        // 配列cのc+1へ移る
        // 3.--(*(++cpp))
        // 配列cのcへ移る
        // 4.*(--(*(++cpp)))
        // 文字列"ENTER"の先頭へ移る
        // 5.*(--(*(++cpp))) + 3
        // "ENTER"の4番要素以降"ER"を指す

        printf("%s\n", *cpp[-2] + 3);
        // 1.cpp[-2]
        // cppで3番要素から1番要素c+3へフォーカスする。移動はせず基準は3番要素c+1のまま
        // 2.*cpp[-2]
        // 配列cのc+3は"FIRST"
        // 2.*cpp[-2] + 3
        // "FIRST"の4番要素以降"ST"を指す

        printf("%s\n", cpp[-1][-1] + 1);
        // 1.cpp[-1]
        // cppで3番要素c+1から2番要素c+2へフォーカスする。基準は3番要素c+1のまま
        // 2.cpp[-1][-1]
        // 配列cのc+2から1つ左へシフトしてc+1をフォーカスする
        // 3.cpp[-1][-1] + 1
        // c+1である"NEW"の2番要素以降"EW"を指す

    return 0;
}
POINT
ER
ST
EW

[C言語] Cパズルブック 6.3 多次元配列

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.3のコードと出力は以下の通りです。

取り立てて難しいところはありませんでした。LLDBデバッガで2次元配列、1次元配列のメモリ格納値を確認しました。

#include "defs.h"

int a[3][3] = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}};
int *pa[3] = {
            a[0], a[1], a[2]};
int *p = a[0];

int main() {
    int i;
    for(i = 0; i < 3; i++){
        PRINT3(d, a[i][2-i], *a[i], *(*(a+i)+i));
    }
    NL;
    
    for(i = 0; i < 3; i++){
        PRINT2(d, *pa[i], p[i]);
    }

    return 0;
}
value = 3	value = 1	value = 1	
value = 5	value = 4	value = 5	
value = 7	value = 7	value = 9	

value = 1	value = 1	
value = 4	value = 2	
value = 7	value = 3
ピンクは整数の2次元配列
緑は各1次元配列の先頭を示すポインタの配列
青は2次元配列の先頭を示すポインタ

[C言語] Cパズルブック 6.2.1-2 ポインタの配列 ポインタのポインタ

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.2.1-2のコードと出力は以下の通りです。フォーマット指定子は10進数dから16進数xに変更しています。

ポインタのポインタを使うことでデータのアドレスを動的に変更でき、変数を効率的に共有できるようになります。

#include "defs.h"

int a[] = {0, 1, 2, 3, 4};
int *p[] = {a, a+1, a+2, a+3, a+4};
int **pp = p;

int main() {
    PRINT2(x, a, *a);
    PRINT3(x, p, *p, **p);
    PRINT3(x, pp, *pp, **pp);

    printf("&pp: %x\n", &pp); //「ポインタのポインタ」アドレス

    return 0;
}
value = 4c04000	value = 0	
value = 4c04018	value = 4c04000	value = 0	
value = 4c04018	value = 4c04000	value = 0	
&pp: 4c04040

配列aのアドレス: 4c04000、格納データ: {0,1,2,3,4}
「ポインタ」アドレス: 4c04018、格納データ: 4c04000
「ポインタのポインタ」アドレス: 4c04040、格納データ: 4c04018

「ポインタ」アドレスの格納データ4c04000を動的に変更して変数を効率的に共有する。

[C言語] Cパズルブック 6.1.5-7 単純なポインタと配列 デクリメント

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.1.5-7のコードと出力は以下の通りです。

いずれも配列の最後の要素から左に向かって読み込んでいくという内容です。

&a[4] と a + 4が同義であるというのが、私にとって新たな知見でした。まあ基本的に添字を使用しますが、AVRマイコンなどメモリ容量が極端に少ない環境では、添字 iの分だけメモリ消費を減らせるので役立ちそうです。

#include "defs.h"

int a[] = {0, 1, 2, 3, 4};

int main() {
    int i, *p;

    // 問6.1.5 配列のポインタを最後の要素4に置き、左に読んでいく
    for (p = a + 4; p >= a; p--){
        printf("*p = %d a[0] = %d\n", *p, a[0]);
        PR(d, *p);
        NL;
    }
    NL;

    // 問6.1.6 配列のポインタを最後の要素4に置き、左に読んでいく(添字を使用)
    for (p = a + 4, i = 0; i <= 4; i++){
        printf("*p = %d i = %d\n", *p, i);
        PR(d, p[-i]);
        NL;
    }
    NL;

    // 問6.1.7 配列のポインタを最後の要素4に置き、左に読んでいく(配列aを使用)
    for (p = a + 4; p >= a; p--){
        printf("*p = %d a[0] = %d\n", *p, a[0]);
        PR(d, a[p - a]);
        NL;
    }

    return 0;
}
*p = 4 a[0] = 0
value = 4	
*p = 3 a[0] = 0
value = 3	
*p = 2 a[0] = 0
value = 2	
*p = 1 a[0] = 0
value = 1	
*p = 0 a[0] = 0
value = 0	

*p = 4 i = 0
value = 4	
*p = 4 i = 1
value = 3	
*p = 4 i = 2
value = 2	
*p = 4 i = 3
value = 1	
*p = 4 i = 4
value = 0	

*p = 4 a[0] = 0
value = 4	
*p = 3 a[0] = 0
value = 3	
*p = 2 a[0] = 0
value = 2	
*p = 1 a[0] = 0
value = 1	
*p = 0 a[0] = 0
value = 0	

[C言語] Cパズルブック 6.1.2-4 単純なポインタと配列

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.1.2-4のコードおよび出力は以下の通りです。

6.1.4のp++は整数1をインクリメントするのではなく、メモリ番地が1番地増えるという意味になります。

6.1.4でメモリ番地が1増えているのにメモリアドレスは4増えているというところが引っかかったので、LLDBデバッガでメモリ格納状態を確認しました。

intのサイズが4バイトのため、メモリアドレスが4増えているということでした。次行のPRマクロでさらに4増えて、結局1つ飛ばしで配列を読み込むことになります。

パズルのために考えられたような問題ですから実務にはあまり役立ちそうにありません。これ以上深追いしないのが吉でしょう。

#include "defs.h"

int a[] = {0, 1, 2, 3, 4};

int main() {
    int i, *p;

    // 問6.1.2 メモリアドレスへアクセス
    for (p = &a[0]; p <= &a[4]; p++){
        PR(d, *p);
    }
    NL;

    // 問6.1.3 ポインタを配列先頭にする
    for (p = &a[0], i = 1; i <= 5; i++){
        PR(d, p[i]);
    }
    NL;
    NL;

    // 問6.1.4 偶数を出力
    for (p = a, i = 0; p+i <= a+4; p++, i++){
        printf("p = %x i = %d a = %x\n", p, i, a);
        PR(d, *(p+i));
        NL;
    }
    NL;

    // 問6.1.4a 全てを出力
    printf("6.1.4a\n");
    for (p = a, i = 0; p+i <= a+4; i++){
        printf("p = %x i = %d a = %x\n", p, i, a);
        PR(d, *(p+i));
        NL;
    }

    printf("Size of p: %zu bytes\n", sizeof(p));
    printf("Size of int: %zu bytes\n", sizeof(int));

    return 0;
}
value = 0	value = 1	value = 2	value = 3	value = 4	
value = 1	value = 2	value = 3	value = 4	value = 0	

p = 482c000 i = 0 a = 482c000
value = 0	
p = 482c004 i = 1 a = 482c000
value = 2	
p = 482c008 i = 2 a = 482c000
value = 4	

6.1.4a
p = 482c000 i = 0 a = 482c000
value = 0	
p = 482c000 i = 1 a = 482c000
value = 1	
p = 482c000 i = 2 a = 482c000
value = 2	
p = 482c000 i = 3 a = 482c000
value = 3	
p = 482c000 i = 4 a = 482c000
value = 4	
Size of p: 8 bytes
Size of int: 4 bytes

[C言語] Cパズルブック 6.1.1 単純なポインタと配列 defs.h

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

第2章から第5章は後回しにしてポインタを扱う第6章に着手します。

まずはマクロをまとめたdefs.hを作成して(教本P37に掲載)、問6.1.1のコードを走らせました。defs.hは現在のclangで使えるよう修正しています。

配列の中身を順次出力するという内容です。

#include <stdio.h>

#define PR(format, value) printf("value = %"#format"\t",value)
// #define PR(format, value) printf("value = %format\t"),(value)) // 1985年の書き方

#define NL putchar('\n') // 改行

#define PRINT1(f, x1) PR(f, x1), NL
#define PRINT2(f, x1, x2) PR(f, x1), PRINT1(f, x2)
#define PRINT3(f, x1, x2, x3) PR(f, x1), PRINT2(f, x2, x3)
#define PRINT4(f, x1, x2, x3, x4) PR(f, x1) PRINT3(f, x2, x3, x4)

// CパズルブックのP37に掲載
#include "defs.h"

int a[] = {0, 1, 2, 3, 4};

int main() {
    int i, *p;

    for (i = 0; i <= 4; i++){
        PR(d, a[i]);
    }
    NL;

    return 0;
}
value = 0	value = 1	value = 2	value = 3	value = 4

[C言語] Cパズルブック 1.6 演算子の優先順位とその評価

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問1.6.1-6の解法は以下の通りです。

プログラミングの学習と言うよりも頭のトレーニング的な感じです。

真偽が確定していれば計算機は作業を止めますが、人間の作業や業務においては無駄な真偽評価をすることが多々あります。モチベーションが下がるだけですね。特に日本人はやりがち。

ようやく第1章が終わりました。次の章をどれにするかは興味本位で決めたいです。

#include <stdio.h>
#define PRINT3(x, y, z) printf("x = %d\ty = %d\tz = %d\n", x, y, z)

int main() {
    int x , y, z;

    // 問1.6.1
    x = y = z = 1;
    ++x || ++y && ++z;
    PRINT3(x, y, z);

    // ++x || (++y && ++z)
    // 計算機は左から評価を行う
    // 計算1: ++xによりx = 2となりTRUE
    // 計算2: ||を読込み、すでに左がTRUEなので計算終了
    // よって、x = 2, y = 1, z = 1

    // 問1.6.2
    x = y = z = 1;
    ++x && ++y || ++z;
    PRINT3(x, y, z);

    // (++x && ++y) || ++z
    // 計算1: ++xによりx = 2となりTRUE
    // 計算2: &&を読込み、++yによりy = 2となりTRUE、左はTRUE
    // 計算3: ||を読込み、すでに左がTRUEなので計算終了
    // よって、x = 2, y = 2, z = 1

    // 問1.6.3
    x = y = z = 1;
    ++x && ++y && ++z;
    PRINT3(x, y, z);

    // (++x && ++y) && ++z
    // 計算1: ++xによりx = 2となりTRUE
    // 計算2: &&を読込み、++yによりy = 2となりTRUE、左はTRUE
    // 計算3: &&を読込み、++zによりz = 2となりTRUE、TRUE && TRUE = TRUE 
    // よって、x = 2, y = 2, z = 2

    // 問1.6.4
    x = y = z = -1;
    ++x && ++y || ++z;
    PRINT3(x, y, z);

    // (++x && ++y) || ++z
    // 計算1: ++xによりx = 0となりFALSE 左はFALSE確定のため++yは評価しない
    // 計算2: ||を読込み、左がFALSEなので次に進む
    // 計算3: ++zによりz = 0となりFALSE、FALSE || FALSE = FALSE
    // よって、x = 0, y = -1, z = 0

    // 問1.6.5
    x = y = z = -1;
    ++x || ++y && ++z;
    PRINT3(x, y, z);

    // ++x || (++y && ++z)
    // 計算1: ++xによりx = 0となりFALSE
    // 計算2: ||を読込み、左がFALSEなので次に進む
    // 計算3: ++yによりy = 0となりFALSE、&&を読込み右はFALSE確定のため++Zは評価しない
    // 計算4: FALSE || FALSE = FALSE
    // よって、x = 0, y = 0, z = -1

    // 問1.6.6
    x = y = z = -1;
    ++x && ++y && ++z;
    PRINT3(x, y, z);

    // (++x && ++y) && ++z
    // 計算1: ++xによりx = 0となりFALSE
    // 計算2: &&を読込み、左はFALSEのためFALSE確定。++yは評価しない
    // 計算3: &&を読込み、左はFALSEのためFALSE確定。++zは評価しない
    // 計算4: FALSE && FALSE = FALSE
    // よって、x = 0, y = -1, z = -1

    return 0;
}
x = 2	y = 1	z = 1
x = 2	y = 2	z = 1
x = 2	y = 2	z = 2
x = 0	y = -1	z = 0
x = 0	y = 0	z = -1
x = 0	y = -1	z = -1

[C言語] Cパズルブック 1.5.4-5 関係演算子と条件演算子 3値の比較

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問1.5.4 -5の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z;

    x = 3; y = z = 4;

    // 問1.5.4
    PRINT((z >= y >= x) ? 1 : 0);

    // PRINT(((z >= y) >= x) ? 1 : 0);
    // 計算1: z >= y
    //       4 >= 4 はTRUE 1
    // 計算2: (z >= y) >= x
    //       1 >= 3 はFALSE
    // 計算3: Lt ? 1 : 0
    //       FALSEは0
    // よって出力は0となる
    // しかし途中で整数がブール値に変わっていて正しい比較になっていない

    // 問1.5.5
    PRINT(z >= y && y >= x);

    // PRINT((z >= y) && (y >= x));
    // 計算1: z >= y
    //       4 >= 4 はTRUE 1
    // 計算2: y >= x
    //       4 >= 3 はTRUE 1
    // 計算3: Lt && Rt
    //       TRUE && TRUEは1
    // よって出力は1となる
    // こちらはブール値の論理ANDを計算する形でありx,y,zの正しい比較になっている

    return 0;
}
0
1