[C言語] Cパズルブック 1.3 論理演算子とインクリメント演算子

『Cパズルブック』(Alan R.Feuer, 1985)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

3番目以降は難問でした。

最後のz = x / ++ xは優先度3の除算と優先度2の前置インクリメントの関係を厳格に用いると先にインクリメントが実行され後で除算になるので4/4の1になりますが、実際の計算結果は3/4の商である0になります。

この計算式については除算が優先されています。gccもclangも同じ結果でした(コンパイラは一応下記警告を発していました)。本の解答はコンパイラ次第なので ? になっています。

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

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

    x = 2; y = 1; z = 0;
    x = x && y || x;        // (TRUE and TRUE) or TRUE = TRUE 1
    PRINT(x);               // 1
    PRINT(x || ! y && z);   // (TRUE or FALSE) or FALSE = TRUE 1

    x = y = 1;
    z = x ++ -1; // z = 1 - 1 後置インクリメントなのでx++評価後にxが2に増加する
    PRINT(x);   // 2
    PRINT(z);   // 0
    z += - x ++ + ++ y; // 0 + -(2) + 2
    PRINT(x);   // 3
    PRINT(z);   // 0
    z = x / ++ x; // z = 3/4 or 4/4
    PRINT(z);   // 0 or 1

    return 0;
}
src/test.c:19:13: warning: unsequenced modification and access to 'x' [-Wunsequenced]
    z = x / ++ x; // 3 / 4
        ~   ^
1 warning generated.

[C言語] Cパズルブック 1.2 代入演算子

『Cパズルブック』(Alan R.Feuer, 1985)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

『Cプログラミングの落とし穴』をようやく読了しました。今後もプログラミングに関する名著を継続して読んでいきます。

次はベル研究所内の教材を元に製作された『Cパズルブック』です。1985年の初版なので記述形式はK&Rスタイルになります。

1.2の3番目はまずy==zで左辺右辺を比較していてイコールが成り立つのでTRUE 1になり、これをxに代入するためPRINTXの出力は1になります。比較演算子==と代入演算子=の使い分けが出題の目的です。

4番目は式自体はx = 1,とy = z = 4の比較でFALSE 0になりますが、xは1のままですから1が出力されます。

第8章までありますが、来年1月中の読了を目指します。これを読み切ったら、以前さらっと目を通しただけのK&R 第2版(ANSI C)に取り組みます。

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

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

    x *= 3 + 2;  // 10
    PRINTX;

    x *= y = z = 4; // 40
    PRINTX;

    x = y == z; // 1
    PRINTX;

    x == ( y = z); // 1
    PRINTX;

    return 0;
}

[C言語] ダブルポインタの概念 **ptr

ダブルポインタの概念を自分なりにまとめました。

ポインタ変数の関連データの中でそのメモリアドレスを表現するために必要な概念であると解釈しました。

ポインタのポインタなので云々とメモリへの格納が数珠つなぎになっている説明図をよく見かけますが、私の場合は余計に学習を妨げるだけでした。

そもそもメモリやCPUなどハードの知識がないとC言語の根本的な理解は厳しいように思います。

[C言語] Cプログラミングの落とし穴 7.6 メモリ番地0 P99

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

本の内容からの派生でchar*とchar[]のメモリアドレスと値、および代入・一部書き換えの可否を調べました。結果は以下の通りです。

char* : 代入可、一部書き換え不可
char[] : 代入不可、一部書き換え可

char*では静的領域にある定数のアドレスがスタック領域に格納され、char[]では定数がそのままスタック領域にコピーされます。

#include <stdio.h>

int main() {
    char *p1 = "abc";
    char p2[] = "xyz";

    printf("p1 : %s\n", p1);
    printf("*p1 %c\n", *p1);
    
    // p1[0] = 'd'; 定数領域の書き換えになるため"Bus error: 10"になる

    printf("p2 : %s\n", p2);
    printf("*p2 %c\n", *p2);

    return 0;
}
p1 : abc
*p1 a
p2 : xyz
*p2 x

[C言語] Cプログラミングの落とし穴 6.4 マクロは型定義ではない P90

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

defineで型定義は一応できるものの、変数宣言はtypedefのようにまとめてはできません。

変数のデータ型はtypeof()関数で取得できますが、これを正式に出力する方法はありません。printfで適当な型として出力させると警告内に型が表示されます。

#include <stdio.h>

#define T1 struct foo *
typedef struct foo *T2;

int main() {
    T1 a; T1 b; // T1 a, b;ではエラーになる
    T2 c, d;

    printf("a : %s\n", a);
    printf("b : %s\n", b);
    printf("c : %s\n", c);
    printf("d : %s\n", d);

    return 0;
}
src/test.c:10:24: warning: format specifies type 'char *' \
but the argument has type 'struct foo *' [-Wformat]
    printf("a : %s\n", a);
                ~~     ^
src/test.c:11:24: warning: format specifies type 'char *' \
but the argument has type 'struct foo *' [-Wformat]
    printf("b : %s\n", b);
                ~~     ^
src/test.c:12:24: warning: format specifies type 'char *' \
but the argument has type 'T2' (aka 'struct foo *') [-Wformat]
    printf("c : %s\n", c);
                ~~     ^
src/test.c:13:24: warning: format specifies type 'char *' \
but the argument has type 'T2' (aka 'struct foo *') [-Wformat]
    printf("d : %s\n", d);
a : (null)
b : (null)
c : (null)
d : (null)

[C言語] Cプログラミングの落とし穴 6.2 マクロは関数ではない P85 その2

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

この本はいわゆるANSI C(C89規格)策定中に書かれており、今読んでいるところについてはC89では修正されているようです。つまり”マクロは関数である”という状態になっています。

下記コードではmaxマクロ内の数値比較と数値選択のところで計2回インクリメントが出現するので、x[1]が飛ばされx[2]にあたる1が選択されて変数biggestは1になってしまうという著者の指摘通りにはなりません。

biggest = ((biggest) > (x[i++])?(biggest) : (x[i++]));

#include <stdio.h>

#define N 3
#define max(a,b) ((a) > (b) ? (a):(b))

int main() {
    int x[N];
    x[0] = 2;
    x[1] = 3;
    x[2] = 1;
    
    int biggest = x[0];

    int i = 0;
    while (i < N){
        biggest = max(biggest, x[i++]);
    }

    printf("biggest : %d\n", biggest);

    return 0;
}
biggest : 3

[C言語] Cプログラミングの落とし穴 6.2 マクロは関数ではない P84

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

少しずつ読み進めています。

#include <stdio.h>

#define abs(x) (((x) >= 0) ? (x):-(x))
#define max(a,b) ((a) > (b) ? (a):(b))

int main() {
    int a = -10;
    int a_abs = abs(a);

    int b = 100;
    int ab_max = max(a, b);

    printf("a_abs : %d\n", a_abs);
    printf("ab_max : %d\n", ab_max);

    return 0;
}
a_abs : 10
ab_max : 100

[C言語] Cプログラミングの落とし穴 4.5 外部の型のチェックP68 境界値分析

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

intの最大値付近が16進数ではどうなっているのか調べました。

intの最大値2147483647の16進数での次の値は10進数では-2147483648となっていて1ずつ増えていき最後に-1になります。この値の次がintの範囲を超えた数値になります。

4バイトをあてがったintの最大値は(2^8)^4/2 -1=2147483647のように算出できます。

int int_max_m1, int_max, int_max_p1, int_m1, int_m1_p1;
long long_m1_p1, long_A;

int main() {
    int_max_m1= 2147483646; // intの最大値 -1
    int_max = 2147483647; // intの最大値
    int_max_p1 = 2147483648; // intの最大値 +1
    int_m1 = -1;
    int_m1_p1 = 0x100000000; // int最大値の次の値 int
    long_m1_p1 = 0x100000000; // int最大値の次の値 long
    long_A = 4294967295; // int:-1


    printf("10進数\n");
    printf("int_max_m1 : %d\n", int_max_m1);
    printf("int_max : %d\n", int_max);
    printf("int_max_p1 : %d\n", int_max_p1);
    printf("int_m1 : %d\n", int_m1);
    printf("int_m1_p1 : %d\n", int_m1_p1);
    printf("long_m1_p1 : %ld\n", long_m1_p1);
    printf("long_A : %ld\n", long_A);

    printf("16進数\n");
    printf("int_max_m1 : %x\n", int_max_m1);
    printf("int_max : %x\n", int_max);
    printf("int_max_p1 : %x\n", int_max_p1);
    printf("int_m1 : %x\n", int_m1);
    printf("int_m1_p1 : %x\n", int_m1_p1);
    printf("long_m1_p1 : %lx\n", long_m1_p1);
    printf("long_A : %lx\n", long_A);

    return 0;
}
10進数
int_max_m1 : 2147483646
int_max : 2147483647
int_max_p1 : -2147483648
int_m1 : -1
int_m1_p1 : 0
long_m1_p1 : 4294967296
long_A : 4294967295
16進数
int_max_m1 : 7ffffffe
int_max : 7fffffff
int_max_p1 : 80000000
int_m1 : ffffffff
int_m1_p1 : 0
long_m1_p1 : 100000000
long_A : ffffffff

[C言語] Cプログラミングの落とし穴 4.5 外部の型のチェックP68

『Cプログラミングの落とし穴』(A.コーニグ, 1990)
[M1 Mac, Big Sur 11.7.2, clang 13.0.0, NO IDE]

2ヶ月と2週間ぶりに戻ってきました。

別ファイルの型宣言が間違っている場合の検証です。

gccでは警告が出て、clangはスルー、いずれもビルドが通りました。相変わらずclangはゆるいです。

# include "test2.h"

int n;

int main() {
    n = 2147483648; // intの最大値 2147483647の次は-2147483648になる(2の補数形式)
    // n = 9223372036854775807; // longの最大値ではエラーになる
    func(n);

    return 0;
}
#include <stdio.h>

extern long n;

void func(long n) {

    printf("n : %ld\n", n);

    return;
}
n : -2147483648
// clangは警告なし

// gccは警告あり
src/test.c:7:9: warning: implicit conversion from 'long' to 'int' changes value from 2147483648 to -2147483648 [-Wconstant-conversion]
    n = 2147483648;
      ~ ^~~~~~~~~~
1 warning generated.
(lldb) memory read -c32 0x0000000100008010
0x100008010: ff ff ff ff ff ff ff 7f 00 00 00 00 00 00 00 00  ................
0x100008020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................