[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  ................

[C++] 将棋エンジン調査 2022年10月 Apple Silicon

[M1 Mac, Big Sur 11.6.8, g++-12 12.2.0, NO IDE]

主な将棋エンジンのApple Siliconへの対応状況を調べました。GUIアプリには”将棋所”を使って動作確認しました。

せっかくC++を学んでいるのですから、将棋エンジンを作らないまでもコードの構造ぐらいは見ておこうと思います。

[C言語] printfで各種検証 シングルクォート LLDB

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

printfでダブルクォート、シングルクォートと、&ありなしの組み合わせにて出力してみました。

シングルクォートでは1文字を囲むことになっていますが、複数文字を囲むと全体で1文字と解釈されリトルエンディアンのルールにより下位アドレスから順に格納されるため並びが逆転します。

C言語についてはおびただしい数のネット情報がありますが、検証する必要がないにしても誰もこういったお遊びをしていませんでした。面白いネタだと思うのですが…

#include <stdio.h>

int main() {
    char c1[] = "abc";
    char* c2 = "def";
    char* c3 = 'ghi';

    printf("c1: %s\n", c1);
    printf("&c1: %s\n", &c1);

    printf("c2: %s\n", c2);
    printf("&c2: %s\n", &c2);

    // printf("c3: %s\n", c3); // Segmentation fault: 11
    printf("&c3: %s\n", &c3);

    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
c1: abc
&c1: abc
c2: def
&c2: P? // ポインタをASCIIコードとして解釈し出力
&c3: ihg // ghiの並びが逆転する

[C言語] LLDBを使ったアセンブリ言語解読 printf

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

LLDBを使ってレジスタ値やメモリ値の変化を把握し、アセンブリ言語の仕組みを確認しました。

アセンブリ言語のコード全55行の1行ごとにregister readコマンド、memory readコマンドを実行して、レジスタ値、スタック作業領域(sp〜fp)の値を出力し、Excelに貼り付けるというなかなか手間のかかる作業でした。

まず変数は静的領域に格納され、関数の引数に使われる変数はスタック領域にコピーします。

そしてレジスタに即値かスタック領域の番地をコピーしてから、関数を呼び出します。

printfの引数でiのように&がついていないものはレジスタに即値がコピーされていて、&cのように&がついているものはスタック領域に格納されている変数の番地がレジスタにコピーされます。

&は”スタック領域にある変数の番地をレジスタにコピーする”目印だということが理解できました。

vmmapコマンドでメモリ領域の内訳等が出力できます。

#include <stdio.h>

int main() {
    int i;
    char c[] = {'a', 'b', 'c', 'd', 'e'};
    char* c_ptr;
    char* c_ptr2;

    i = 1000;
    c_ptr = 'xyz';
    c_ptr2 = "stu"; // ダブルクォートではstuが格納されている静的領域メモリ番地がスタック領域にコピーされる

    printf("i: %d\n", i);
    printf("c: %s\n", &c);
    printf("c_ptr: %s\n", &c_ptr);
    printf("c_ptr2: %s\n", &c_ptr2);

    return 0;
}
test`main:
0x100003e48 <+0>:   sub    sp, sp, #0x60             ; =0x60 
0x100003e4c <+4>:   stp    x29, x30, [sp, #0x50]
0x100003e50 <+8>:   mov    w8, #0x0
0x100003e54 <+12>:  str    w8, [sp, #0x2c]
0x100003e58 <+16>:  str    wzr, [sp, #0x4c]
0x100003e5c <+20>:  adrp   x8, 0
0x100003e60 <+24>:  add    x8, x8, #0xf50            ; =0xf50 
0x100003e64 <+28>:  ldr    w9, [x8]
0x100003e68 <+32>:  add    x10, sp, #0x40            ; =0x40 
0x100003e6c <+36>:  str    x10, [sp, #0x10]
0x100003e70 <+40>:  str    w9, [sp, #0x40]
0x100003e74 <+44>:  ldrb   w8, [x8, #0x4]
0x100003e78 <+48>:  strb   w8, [sp, #0x44]
0x100003e7c <+52>:  mov    w8, #0x3e8
0x100003e80 <+56>:  str    w8, [sp, #0x48]
0x100003e84 <+60>:  add    x8, sp, #0x38             ; =0x38 
0x100003e88 <+64>:  str    x8, [sp, #0x18]
0x100003e8c <+68>:  mov    x8, #0x797a
0x100003e90 <+72>:  movk   x8, #0x78, lsl #16
0x100003e94 <+76>:  str    x8, [sp, #0x38]
0x100003e98 <+80>:  add    x8, sp, #0x30             ; =0x30 
0x100003e9c <+84>:  str    x8, [sp, #0x20]
0x100003ea0 <+88>:  adrp   x8, 0
0x100003ea4 <+92>:  add    x8, x8, #0xf55            ; =0xf55 
0x100003ea8 <+96>:  str    x8, [sp, #0x30]
0x100003eac <+100>: ldr    w9, [sp, #0x48]
0x100003eb0 <+104>: mov    x8, x9
0x100003eb4 <+108>: adrp   x0, 0
0x100003eb8 <+112>: add    x0, x0, #0xf59            ; =0xf59 
0x100003ebc <+116>: mov    x9, sp
0x100003ec0 <+120>: str    x8, [x9]
0x100003ec4 <+124>: bl     0x100003f20               ; symbol stub for: printf
0x100003ec8 <+128>: ldr    x8, [sp, #0x10]
0x100003ecc <+132>: adrp   x0, 0
0x100003ed0 <+136>: add    x0, x0, #0xf60            ; =0xf60 
0x100003ed4 <+140>: mov    x9, sp
0x100003ed8 <+144>: str    x8, [x9]
0x100003edc <+148>: bl     0x100003f20               ; symbol stub for: printf
0x100003ee0 <+152>: ldr    x8, [sp, #0x18]
0x100003ee4 <+156>: adrp   x0, 0
0x100003ee8 <+160>: add    x0, x0, #0xf67            ; =0xf67 
0x100003eec <+164>: mov    x9, sp
0x100003ef0 <+168>: str    x8, [x9]
0x100003ef4 <+172>: bl     0x100003f20               ; symbol stub for: printf
0x100003ef8 <+176>: ldr    x8, [sp, #0x20]
0x100003efc <+180>: adrp   x0, 0
0x100003f00 <+184>: add    x0, x0, #0xf72            ; =0xf72 
0x100003f04 <+188>: mov    x9, sp
0x100003f08 <+192>: str    x8, [x9]
0x100003f0c <+196>: bl     0x100003f20               ; symbol stub for: printf
0x100003f10 <+200>: ldr    w0, [sp, #0x2c]
0x100003f14 <+204>: ldp    x29, x30, [sp, #0x50]
0x100003f18 <+208>: add    sp, sp, #0x60             ; =0x60 
0x100003f1c <+212>: ret  
~ $ vmmap 28644
Process:         test [28644]
Path:            /Volumes/VOLUME/*/test
Load Address:    0x100000000
Identifier:      test
Version:         ???
Code Type:       ARM64
Platform:        macOS
Parent Process:  debugserver [28645]

Date/Time:       2022-10-08 15:55:12.687 +0900
Launch Time:     2022-10-08 15:54:41.754 +0900
OS Version:      macOS 11.6.1 (20G224)
Report Version:  7
Analysis Tool:   /Applications/Xcode.app/Contents/Developer/usr/bin/vmmap
Analysis Tool Version:  Xcode 13.2.1 (13C100)

Physical footprint:         865K
Physical footprint (peak):  865K
----

Virtual Memory Map of process 28644 (test)
Output report format:  2.4  -- 64-bit process
VM page size:  16384 bytes

==== Non-writable regions for process 28644
REGION TYPE                    START - END         [ VSIZE  RSDNT  DIRTY   SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
__TEXT                      100000000-100004000    [   16K    16K    16K     0K] r-x/rwx SM=COW          /Volumes/VOLUME/*/test
__DATA_CONST                100004000-100008000    [   16K    16K    16K     0K] r--/rw- SM=COW          /Volumes/VOLUME/*/test
__LINKEDIT                  10000c000-100010000    [   16K    16K     0K     0K] r--/r-- SM=COW          /Volumes/VOLUME/*/test
__TEXT                      100010000-100024000    [   80K    80K     0K     0K] r-x/r-x SM=COW          /usr/lib/dyld
__TEXT                      100024000-100028000    [   16K    16K    16K     0K] r-x/rwx SM=COW          /usr/lib/dyld
__TEXT                      100028000-100090000    [  416K   368K     0K     0K] r-x/r-x SM=COW          /usr/lib/dyld
__DATA_CONST                100090000-100098000    [   32K    16K    16K    16K] r--/rw- SM=COW          /usr/lib/dyld
__LINKEDIT                  1000d0000-100114000    [  272K   240K     0K     0K] r--/r-- SM=COW          /usr/lib/dyld
__LINKEDIT                  100114000-100118000    [   16K     0K     0K     0K] r--/r-- SM=NUL          /usr/lib/dyld
shared memory               100120000-100124000    [   16K    16K    16K     0K] r--/r-- SM=SHM  
MALLOC metadata             100124000-100128000    [   16K    16K    16K     0K] r--/rwx SM=ZER          ...cZone_0x100124000 zone structure
MALLOC guard page           10012c000-100130000    [   16K     0K     0K     0K] ---/rwx SM=ZER  
MALLOC guard page           100138000-10013c000    [   16K     0K     0K     0K] ---/rwx SM=ZER  
MALLOC guard page           10013c000-100140000    [   16K     0K     0K     0K] ---/rwx SM=NUL  
MALLOC guard page           100148000-100150000    [   32K     0K     0K     0K] ---/rwx SM=NUL  
MALLOC guard page           100158000-10015c000    [   16K     0K     0K     0K] ---/rwx SM=NUL  
MALLOC metadata             10015c000-100160000    [   16K    16K    16K     0K] r--/rwx SM=PRV  
STACK GUARD                 16be00000-16f604000    [ 56.0M     0K     0K     0K] ---/rwx SM=NUL          stack guard for thread 0
__TEXT                      18e5be000-18e5c0000    [    8K     8K     0K     0K] r-x/r-x SM=COW          ...ib/system/libsystem_blocks.dylib
__TEXT                      18e5c0000-18e5f8000    [  224K   224K     0K     0K] r-x/r-x SM=COW          /usr/lib/system/libxpc.dylib
__TEXT                      18e5f8000-18e610000    [   96K    96K     0K     0K] r-x/r-x SM=COW          ...lib/system/libsystem_trace.dylib

[C言語] レジスタ値の変化

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

アセンブル言語のコードを解読するため、LLDBを使ってレジスタ値の変化を表にまとめました。黄色のセルで値が変わっています。

register readコマンドでmain関数先頭行でのレジスタ値を出力し、nextコマンドで1行ずつ進めて都度レジスタ値を出力しました。

#include <stdio.h>

int main() {
    int i = 1000;
    char c[6] = {'a', 'b', 'c', 'd', 'e'};
    char* c_ptr = 'xyz';
    char* c_ptr2 = "stu"; // あえてダブルクォートを使用

    printf("i: %d\n", i);
    printf("c: %s\n", &c);
    printf("c_ptr: %s\n", &c_ptr);
    printf("c_ptr2: %s\n", &c_ptr2);

    return 0;
}

[C言語] 変数の格納アドレス検証 char

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]
# LLDB # コマンド

LLDBを使って変数のアドレスを調べています。

ゆくゆくはアセンブリ言語で簡単なコードを書くつもりなので、C++ではなくC言語で書いていきます。

今回はchar、char*について調べました。ポインタ自身のアドレスがわからなかったのですが、memory readコマンドの結果を照らし合わせて突き止めることができました。

下図の青線で囲んだところがポインタのようです。メモリアドレスが書かれています。変数にほぼ隣接した下位側に位置しています。

#include <stdio.h>

int main() {
    int i = 1000;
    char c[6] = {'a', 'b', 'c', 'd', 'e'};
    char* c_ptr = 'xyz';

    printf("i: %d\n", i);
    printf("c: %s\n", &c);
    printf("c_ptr: %s\n", &c_ptr);

    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
i: 1000
c: abcde
c_ptr: zyx // なぜ逆さになるのかは不明

[C/C++] 変数の格納アドレス検証 string

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

変数の格納アドレスを検証していきます。

LLDBを使って確認しました。

#include <stdio.h>
#include <iostream>
#include <string>

using std::cout; using std::endl;
using std::to_string;

int main() {
    int i = 256;
    char c[6] = {'a', 'b', 'c', 'd', 'e'};
    std::string str("abcde");

    cout << "iのアドレス: " << &i << endl;
    cout << "iのサイズ: " << to_string(sizeof i) << endl;

    cout << "cのアドレス: " << &c << endl;
    cout << "cのサイズ: " << to_string(sizeof c) << endl;

    cout << "strのアドレス: " << &str << endl;
    cout << "strのサイズ: " << to_string(sizeof str) << endl;

    return 0;
}

[C/C++] バッファオーバーフロー対策 -fstack-protector-allの実用性を検証 その2

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

引き続き、バッファオーバーフロー対策であるclang++の-fstack-protector-allオプションについて実用性を検証しました。

前回得られたデータから、char変数を増やしていくとやがてint変数のメモリ領域を上書きしてしまうと予想しましたが、実際にそうなるのか検証しました。

変数i予想値の算出方法:
2進数 1101000 00000000 00000000 00000000
反転 0010111 11111111 11111111 11111111
1加算 0011000 00000000 00000000 00000000
10進数 -402653184

その結果、char変数の数に関係なく、int変数との間に複数の空アドレスが配置されることが判明しました。

さすがに文法さえ正しければコードは正常に動くようです。

scanf関数の検証は今度こそこれで終わりにします。

#include <cppstd.h>

int main() {
    int i;
    char c[2];
    char c2[2];
    char c3[2];
    char c4[2];
    char c5[2];
    char c6[2];

    for (i=0; i< 10; i++){
        printf("for文先頭直後 printf i: %d\n",i);
        // rewind(stdin); // ストリームバッファをクリア

        scanf("%s", &c);
        cout << "cout c: " << c << endl;
        printf("printf c: %s\n",&c);

        scanf("%s", &c2);
        cout << "cout c2: " << c2 << endl;
        printf("printf c2: %s\n",&c2);

        scanf("%s", &c3);
        cout << "cout c3: " << c3 << endl;
        printf("printf c3: %s\n",&c3);

        scanf("%s", &c4);
        cout << "cout c4: " << c4 << endl;
        printf("printf c4: %s\n",&c4);

        scanf("%s", &c5);
        cout << "cout c5: " << c5 << endl;
        printf("printf c5: %s\n",&c5);

        scanf("%s", &c6);
        cout << "cout c6: " << c6 << endl;
        printf("printf c6: %s\n",&c6);
        
        printf("printf i: %d\n",i);
       
        cout << "for文終端" << endl;
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
for文先頭直後 printf i: 0
a
cout c: a
printf c: a
b
cout c2: b
printf c2: b
c
cout c3: c
printf c3: c
d
cout c4: d
printf c4: d
e
cout c5: e
printf c5: e
f
cout c6: f
printf c6: f
printf i: 0
for文終端
for文先頭直後 printf i: 1