[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法正常 aを連続入力成功

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

ようやく正常な動作の紹介にたどりつきました。

きりがないのでscanf編はこれくらいにしておきます。

#include <cppstd.h>

int main() {
    int i;
    char c[2];

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

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

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

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

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16f01f698
iのサイズ: 4
cのアドレス: 0x16f01f696
cのサイズ: 2
for文先頭直後 printf i: 0
a
cout c: a
printf c: a
printf i: 0
for文先頭直後 printf i: 1
a
cout c: a
printf c: a
printf i: 1
for文先頭直後 printf i: 2

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法正常 aを連続入力

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

これまで扱ってきた不完全なコードについてscanf関数の引数を%sに修正しました。

しかし挙動はおかしいままでした。実はchar cが確保するメモリ領域が足りていません。ヌル文字の分も確保する必要があります。

ストリームバッファに改行コードが残っているはずなのにループが暴走しないのは何故なのか分かりません。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        printf("for文開始直後 printf i: %d\n",i);
        // rewind(stdin); // ストリームバッファをクリア
        scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16b0df698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
for文開始直後 printf i: 0
a
cout c: a
printf c: 97
printf i: 0
for文開始直後 printf i: 1
a
cout c: a
printf c: 97
printf i: 0
for文開始直後 printf i: 1

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法ミス abcを入力 ストリームバッファ

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

不完全なプログラムのscanfにabcを入力してみました。

ストリームバッファに改行コードが残っているためにループが止まらなくなり、i=5に達して終了しました。

ストリームバッファのデータは行方不明です。おそらく%dと一致しないデータ型を入力したため、undefinedな挙動になったものと思われます。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        printf("for文開始直後 printf i: %d\n",i);
        // rewind(stdin); // ストリームバッファをクリア
        scanf("%d",&c);
        // 正しくは
        // scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16eeeb698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
for文開始直後 printf i: 0
abc
cout c: 
printf c: 0
printf i: 0
for文開始直後 printf i: 1
cout c: 
printf c: 0
printf i: 1
for文開始直後 printf i: 2
cout c: 
printf c: 0
printf i: 2
for文開始直後 printf i: 3
cout c: 
printf c: 0
printf i: 3
for文開始直後 printf i: 4
cout c: 
printf c: 0
printf i: 4

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法ミス 100を連続入力 ストリームバッファ

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

不具合のあるプログラムに100を連続入力した場合の挙動を図にまとめました。

int iは1に増えますが、結局ヌル文字で上書きされるため、無限ループのようになります。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        printf("for文開始直後 printf i: %d\n",i);

        scanf("%d",&c);
        // 正しくは
        // scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16fddf698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
for文開始直後 printf i: 0
100
cout c: d
printf c: 100
printf i: 0
for文開始直後 printf i: 1
100
cout c: d
printf c: 100
printf i: 0
for文開始直後 printf i: 1

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法ミス 1000入力 ストリームバッファ

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

前回、scanfの引数を%sにすべきところを%dにしてしまった場合のプログラムの挙動を図に表現しました。

一部不正確なところがあったので改良版を作成しました。ストリームバッファの存在を明示すると実際により近い説明ができます。

scanfではストリームバッファに改行コードが残ってしまい、後々悪さをすることがあります。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        scanf("%d",&c);
        // 正しくは
        // scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16f56f698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
1000
cout c: ?
printf c: -24
printf i: 3

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法ミス 1000入力

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

前回の続きです。

不出来なコードにscanfで1000を入力した場合は、以下のようになります。

coutでは格納されたデータはASCIIコードとして認識し、printfでは%dなので整数として認識しています。

%dは符号あり整数(8ビットなので-128以上127以下)です。入力値によってはマイナスになったりします。

2進数 11101000の符号あり整数への変換(マイナスと仮定)
1を減算 11100111
反転 00011000
10進数へ変換 24
したがって 2進数 11101000は-24

ASCIIコードは0から127までの128文字ですから、-24の場合は該当文字がありません。

結局メモリに格納された2進数をどのように解釈するか、どのデータ型として読み出すかで出力内容が変わってきます。

%sを%dとしてしまうのをエラーにしないと前回、今回のようなトラブルになりますし、いっそのことコンパイルエラーにしてもらいたいものです。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        scanf("%d",&c);
        // 正しくは
        // scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("%d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16f56f698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
1000
cout c: ?
printf c: -24
3

[C/C++] Cプログラミングの落とし穴 4.4 実引数 P68 scanf文法ミス 10000入力

フォーマット指定子を間違えた場合にプログラムがどういった挙動を示すのか検証しました。

scanf関数で%sと入力すべきところを%dとした場合、データ型がcharからintに変わるためデータサイズが増えてしまい、ややこしいことになります。

とりあえず図にまとめてみました。新・明解C言語に出てきそうな見た目です。

実はchar cの格納領域を1バイト確保するのは間違いで、末尾のヌル文字を考慮した2バイト確保が正解になります。本来はchar c[2]と書くべきですが、そのままにしています。

今回は10000を入力した場合を扱いましたが、100や1000を入れるとまた違った趣きの出力になります。気が向けば次回以降記事にします。

#include <cppstd.h>

int main() {
    int i;
    char c;

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

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

    for (i=0;i<5;i++){
        scanf("%d",&c);
        // 正しくは
        // scanf("%s",&c);

        cout << "cout c: " << c << endl;
        printf("printf c: %d\n",c);

        printf("printf i: %d\n",i);
       
    }
    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
iのアドレス: 0x16f633698
iのサイズ: 4
cのアドレス: 
cのサイズ: 1
10000
cout c: 
printf c: 16
printf i: 39

[アセンブリ] sファイルの改良 printf

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

cppファイルから作成したsファイルについて素人目線で無駄と思える行を削除、メモリ番地を勝手に節約してみました。

下記コードでも一応動きました。スタックポインタを32番地マイナスの位置に移動するところを16番地に、フレームポインタ他をスタックポインタ+16番地に格納するところを+8番地に変更しています。

副作用は不明です。

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 12, 1
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #16                    ; スタックポインタ(sp)から16番地減算してspに格納
	stp	x29, x30, [sp, #8]             ; fp(x29)とlr(x30)をsp+8番地に格納 
	.cfi_def_cfa_offset 32
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	;mov	w8, #0                          ; 0をw8へコピー(削除)
	;str	w8, [sp, #8]                    ; 0をsp+8番地に格納(削除)
	;str	wzr, [sp, #12]                  ; 0をsp+12番地に格納(削除)
	adrp	x0, l_.str@PAGE             ; x0にl_.str@PAGEを書き込み
	add	x0, x0, l_.str@PAGEOFF          ; x0にl_.str@PAGEOFFを追記
	bl	_printf                         ; printfを呼び出し
	mov	w0, #0                          ; 0をw0へコピー(初期化)
	ldp	x29, x30, [sp, #8]             ; sp+8の値をfp,lrに読み込む(元に戻す)
	add	sp, sp, #16                     ; スタックポインタ(sp)から16番地加算してspに格納
	ret
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"\343\203\206\343\202\271\343\203\210"

.subsections_via_symbols

; SP他の番地移動単位は8の倍数にしないとエラーになる
;asm/test.s:9:21: error: index must be a multiple of 8 in range [-512, 504].
; stp x29, x30, [sp, #2] ; fp(x29)とlr(x30)をsp+2番地に格納
;asm/test.s:20:21: error: index must be a multiple of 8 in range [-512, 504].
; ldp x29, x30, [sp, #2] ; sp+16の値をfp,lrに読み込む(元に戻す)
;make: *** [obj/test.o] Error 1
#include <stdio.h>

int main() {

    printf("テスト");

    return 0;
}

[C/C++] Cプログラミングの落とし穴 3.8 演算子 P52

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

ビット演算子&で計算してみました。各桁のビット同士で論理積を算出します。

10と12をビットAND計算すると8になります。

#include <cppstd.h>

int main() {
    int x = 10;
    int y = 12;

    int z = 10 & 12;

    bitset<8> x_bs(x);
    bitset<8> y_bs(y);
    bitset<8> z_bs(z);

    cout << "x_bs = " << x_bs << endl; 
    cout << "y_bs = " << y_bs << endl; 
    cout << "z_bs = " << z_bs << endl; 
    cout << "z = " << z << endl;

    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
x_bs = 00001010
y_bs = 00001100
z_bs = 00001000
z = 8

[C/C++] Cプログラミングの落とし穴 3.7 評価順序 P50

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

代入式にインクリメントを含めると予想外な挙動が見られます。インクリメントは代入式の後に単独で書くべきだとしています。

確かにそのようなコードを書いてみると、法則性がよくわからない結果になりました。

#include <cppstd.h>

int main() {
    int x[5] = {1,2,3,4,5};
    int y[3];

    int i = 0;
    while (i < 3){
        cout << "x[" << i << "] = " << x[i] << endl;
        y[i] = x[i];
        i++;
    }

    cout << "y[0] = " << y[0] << endl;
    cout << "y[1] = " << y[1] << endl; 
    cout << "y[2] = " << y[2] << endl; 

    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
x[0] = 1
x[1] = 2
x[2] = 3
y[0] = 1
y[1] = 2
y[2] = 3
#include <cppstd.h>

int main() {
    int x[5] = {1,2,3,4,5};
    int y[3];

    int i = 1;
    while (i < 4){
        cout << "x[" << i << "] = " << x[i] << endl;
        y[i] = x[i++];
    }

    cout << "y[0] = " << y[0] << endl;
    cout << "y[1] = " << y[1] << endl; 
    cout << "y[2] = " << y[2] << endl; 

    return 0;
}
--------------------------------------------------
出力
--------------------------------------------------
x[1] = 2
x[2] = 3
x[3] = 4
y[0] = 0
y[1] = 0
y[2] = 2