sizeof演算子のまとめ(配列の型調整)
sizeof演算子の続きです。
関数のプロトタイプ宣言で配列型を指定された引数は、関数の仮引数ではポインタ型として扱われる、というお話です。
1 /* sizeof_func.c */ 2 #include <stdio.h> 3 4 void func(char buf[]){ 5 printf("sizeof(buf)@func = %zu\n", sizeof(buf)); 6 } 7 8 int main(int argv, char *argc[]){ 9 10 char buf[] = "0123456789"; 11 12 printf("sizeof(buf)@main = %zu\n", sizeof(buf)); 13 func(buf); 14 15 return 0; 16 }
実行結果
sizeof(buf)@main = 11 sizeof(buf)@func = 8
main関数内で宣言された配列変数bufのサイズは11バイトですが、関数funcの引数(char []型)にbufを指定し、関数内でのbufのサイズを参照すると、8バイト(ポインタ型のサイズ)となりました。これは、関数のプロトタイプ宣言において、引数の型を配列型に指定しても、関数内部での仮引数はポインタ型として扱われるためです(型調整)。
配列型とポインタ型の違いとして、「配列型変数は左辺値になりえないが、ポインタ型変数は左辺値になりうる」というのがあります。そこで、func内で当該の仮引数が左辺値になりうるかを確かめるために、bufを左辺値とする式を書いてみます。
1 /* sizeof_func.c */ 2 #include <stdio.h> 3 4 char *g_buf = "ABCDEFGHIJ"; 5 6 void func(char buf[]){ 7 printf("g_buf = %p (%s) @ %p\n\n", g_buf, g_buf, &g_buf); 8 9 printf("buf = %p (%s) @ %p\n\n", buf, buf, &buf); 10 buf = g_buf; /* ★仮引数bufを左辺値にして代入 */ 11 printf(" buf <= g_buf\n\n"); 12 printf("buf = %p (%s) @ %p\n", buf, buf, &buf); 13 } 14 15 int main(int argv, char *argc[]){ 16 17 char buf[] = "0123456789"; 18 19 20 printf("buf[] = %s ( ---- ) @ %p\n", buf, &buf); 21 printf("-----------------------------\n"); 22 func(buf); 23 printf("-----------------------------\n"); 24 printf("buf[] = %s ( ---- ) @ %p\n", buf, &buf); 25 26 return 0; 27 }
実行結果
buf[] = 0123456789 ( ---- ) @ 0x7fffb64f7180 …(1) ----------------------------- g_buf = 0x400718 (ABCDEFGHIJ) @ 0x600a80 buf = 0x7fffb64f7180 (0123456789) @ 0x7fffb64f7148 …(2) buf <= g_buf …(3) buf = 0x400718 (ABCDEFGHIJ) @ 0x7fffb64f7148 …(4) ----------------------------- buf[] = 0123456789 ( ---- ) @ 0x7fffb64f7180 …(5)
(1)実引数bufは、アドレス「0x7fffb64f7180」番地を格納領域とする配列型変数です。その領域には、「"0123456789"」という文字列が格納されています。
(2)関数funcに、実引数bufの格納領域のアドレス(0x7fffb64f7180)を渡しました(このアドレスは、関数main内スタックフレーム上のある場所を指すものです)。その結果、関数func内スタックフレーム上の領域(0x7fffb64f7148)に、実引数buf領域のアドレス(0x7fffb64f7180)が格納されたことになります。
(3)g_bufの値(0x400718("ABCDEFGHIJ"))を、仮引数bufの領域(0x7fffb64f7148)に格納します。
(4)(3)より、仮引数buf(領域0x7fffb64f7148)には、"ABCDEFGHIJ"のアドレス(0x400718)が格納されます。
(5)実引数buf(領域0x7fffb64f7180)には、「"0123456789"」が格納されたままとなります。
つまり、関数内での仮引数bufには、代入前は"0123456789"のアドレスが格納されていましたが、代入後は"ABCDE"のアドレスが格納されていることになります。これらはスタック上の仮引数に対する操作であるため、呼び出し元のmain関数のスタックフレーム内の領域(実引数buf)には何も影響を与えていません。そのため、main関数において、関数func呼出し前後で実引数bufの内容を表示すると、その内容は変化していません。
以上を表にまとめると、以下の通りとなります。
(変化した値の背景色を変更しています)
関数func内における「buf = g_buf」の代入前
変数名 | アドレス | 左記に格納された値 | ポインタ値が指す領域の値 |
---|---|---|---|
実引数buf[] | 0x0x7fff01ea9bf0 | "0123456789" | − |
仮引数*buf | 0x0x7fff01ea9bc8 | 0x0x7fff01ea9bf0 | "0123456789" |
変数g_buf | 0x0x400748 | "ABCDEFGHIJ" | − |
関数func内における「buf = g_buf」の代入後
変数名 | アドレス | 左記に格納された値 | ポインタ値が指す領域の値 |
---|---|---|---|
実引数buf[] | 0x0x7fff01ea9bf0 | "0123456789" | − |
仮引数*buf | 0x0x7fff01ea9bc8 | 0x0x400748 | "ABCDEFGHIJ" |
変数g_buf | 0x0x400748 | "ABCDEFGHIJ" | − |
というわけで、関数func内での仮引数bufへの値代入に成功したということは、仮引数bufは左辺値になりうる…つまり、関数内の配列型変数はポインタ変数に型調整されていることになります。
sizeof演算子のまとめ
sizeof演算子について、いろいろ試してみました。アセンブリコードを見てみると、sizeofオペランドの式はコンパイル時に定数に置換されるか、sizeofの計算に影響を与えない数式の場合、式自体が翻訳時に捨てられているのが分かります。
00000000004004e3 <main>: int main(int argc, char *argv[]){ 4004e3: 55 push rbp 4004e4: 48 89 e5 mov rbp,rsp 4004e7: 48 83 ec 30 sub rsp,0x30 4004eb: 89 7d dc mov DWORD PTR [rbp-0x24],edi 4004ee: 48 89 75 d0 mov QWORD PTR [rbp-0x30],rsi char buf[]="ABCDE"; 4004f2: c7 45 f0 41 42 43 44 mov DWORD PTR [rbp-0x10],0x44434241 4004f9: 66 c7 45 f4 45 00 mov WORD PTR [rbp-0xc],0x45 int i=0; 4004ff: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0 int n=0; 400506: c7 45 ec 00 00 00 00 mov DWORD PTR [rbp-0x14],0x0 char c; printf("sizeof(\"\") = %zu\n", sizeof("")); 40050d: b8 18 08 40 00 mov eax,0x400818 400512: be 01 00 00 00 mov esi,0x1 400517: 48 89 c7 mov rdi,rax 40051a: b8 00 00 00 00 mov eax,0x0 40051f: e8 94 fe ff ff call 4003b8 <printf@plt> printf("sizeof(\"ABCDE\") = %zu\n", sizeof("ABCDE")); 400524: b8 2a 08 40 00 mov eax,0x40082a 400529: be 06 00 00 00 mov esi,0x6 40052e: 48 89 c7 mov rdi,rax 400531: b8 00 00 00 00 mov eax,0x0 400536: e8 7d fe ff ff call 4003b8 <printf@plt> printf("sizeof(buf) = %zu\n", sizeof(buf)); 40053b: b8 41 08 40 00 mov eax,0x400841 400540: be 06 00 00 00 mov esi,0x6 400545: 48 89 c7 mov rdi,rax 400548: b8 00 00 00 00 mov eax,0x0 40054d: e8 66 fe ff ff call 4003b8 <printf@plt> printf("sizeof('A') = %zu\n", sizeof('A')); 400552: b8 54 08 40 00 mov eax,0x400854 400557: be 04 00 00 00 mov esi,0x4 40055c: 48 89 c7 mov rdi,rax 40055f: b8 00 00 00 00 mov eax,0x0 400564: e8 4f fe ff ff call 4003b8 <printf@plt> printf("sizeof(node) = %zu\n", sizeof(node)); 400569: b8 67 08 40 00 mov eax,0x400867 40056e: be 08 00 00 00 mov esi,0x8 400573: 48 89 c7 mov rdi,rax 400576: b8 00 00 00 00 mov eax,0x0 40057b: e8 38 fe ff ff call 4003b8 <printf@plt> printf("i = %d\n", i); 400580: b8 7b 08 40 00 mov eax,0x40087b 400585: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] 400588: 89 d6 mov esi,edx 40058a: 48 89 c7 mov rdi,rax 40058d: b8 00 00 00 00 mov eax,0x0 400592: e8 21 fe ff ff call 4003b8 <printf@plt> printf("sizeof(i++) = %zu\n", sizeof(i++)); 400597: b8 83 08 40 00 mov eax,0x400883 40059c: be 04 00 00 00 mov esi,0x4 4005a1: 48 89 c7 mov rdi,rax 4005a4: b8 00 00 00 00 mov eax,0x0 4005a9: e8 0a fe ff ff call 4003b8 <printf@plt> printf("i = %d\n", i); 4005ae: b8 7b 08 40 00 mov eax,0x40087b 4005b3: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] 4005b6: 89 d6 mov esi,edx 4005b8: 48 89 c7 mov rdi,rax 4005bb: b8 00 00 00 00 mov eax,0x0 4005c0: e8 f3 fd ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 4005c5: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 4005c8: b8 96 08 40 00 mov eax,0x400896 4005cd: 89 d6 mov esi,edx 4005cf: 48 89 c7 mov rdi,rax 4005d2: b8 00 00 00 00 mov eax,0x0 4005d7: e8 dc fd ff ff call 4003b8 <printf@plt> printf("sizeof(int [n++]) = %zu\n", sizeof(int [n++])); 4005dc: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 4005df: 89 c2 mov edx,eax 4005e1: 48 63 d2 movsxd rdx,edx 4005e4: 48 c1 e2 02 shl rdx,0x2 4005e8: 83 c0 01 add eax,0x1 4005eb: 89 45 ec mov DWORD PTR [rbp-0x14],eax 4005ee: b8 9e 08 40 00 mov eax,0x40089e 4005f3: 48 89 d6 mov rsi,rdx 4005f6: 48 89 c7 mov rdi,rax 4005f9: b8 00 00 00 00 mov eax,0x0 4005fe: e8 b5 fd ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 400603: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 400606: b8 96 08 40 00 mov eax,0x400896 40060b: 89 d6 mov esi,edx 40060d: 48 89 c7 mov rdi,rax 400610: b8 00 00 00 00 mov eax,0x0 400615: e8 9e fd ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 40061a: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 40061d: b8 96 08 40 00 mov eax,0x400896 400622: 89 d6 mov esi,edx 400624: 48 89 c7 mov rdi,rax 400627: b8 00 00 00 00 mov eax,0x0 40062c: e8 87 fd ff ff call 4003b8 <printf@plt> printf("sizeof(int [++n]) = %zu\n", sizeof(int [++n])); 400631: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 400634: 83 c0 01 add eax,0x1 400637: 89 45 ec mov DWORD PTR [rbp-0x14],eax 40063a: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 40063d: 48 98 cdqe 40063f: 48 8d 14 85 00 00 00 lea rdx,[rax*4+0x0] 400646: 00 400647: b8 b7 08 40 00 mov eax,0x4008b7 40064c: 48 89 d6 mov rsi,rdx 40064f: 48 89 c7 mov rdi,rax 400652: b8 00 00 00 00 mov eax,0x0 400657: e8 5c fd ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 40065c: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 40065f: b8 96 08 40 00 mov eax,0x400896 400664: 89 d6 mov esi,edx 400666: 48 89 c7 mov rdi,rax 400669: b8 00 00 00 00 mov eax,0x0 40066e: e8 45 fd ff ff call 4003b8 <printf@plt> printf("sizeof(c) = %zu\n", sizeof(c)); 400673: b8 d0 08 40 00 mov eax,0x4008d0 400678: be 01 00 00 00 mov esi,0x1 40067d: 48 89 c7 mov rdi,rax 400680: b8 00 00 00 00 mov eax,0x0 400685: e8 2e fd ff ff call 4003b8 <printf@plt> printf("sizeof(c+0) = %zu\n", sizeof(c+0)); 40068a: b8 e1 08 40 00 mov eax,0x4008e1 40068f: be 04 00 00 00 mov esi,0x4 400694: 48 89 c7 mov rdi,rax 400697: b8 00 00 00 00 mov eax,0x0 40069c: e8 17 fd ff ff call 4003b8 <printf@plt> /* printf("sizeof(ia) = %zu\n", sizeof(ia)); */ printf("sizeof(main) = %zu\n", sizeof(main)); 4006a1: b8 f4 08 40 00 mov eax,0x4008f4 4006a6: be 01 00 00 00 mov esi,0x1 4006ab: 48 89 c7 mov rdi,rax 4006ae: b8 00 00 00 00 mov eax,0x0 4006b3: e8 00 fd ff ff call 4003b8 <printf@plt> printf("sizeof(&main) = %zu\n", sizeof(&main)); 4006b8: b8 08 09 40 00 mov eax,0x400908 4006bd: be 08 00 00 00 mov esi,0x8 4006c2: 48 89 c7 mov rdi,rax 4006c5: b8 00 00 00 00 mov eax,0x0 4006ca: e8 e9 fc ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 4006cf: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 4006d2: b8 96 08 40 00 mov eax,0x400896 4006d7: 89 d6 mov esi,edx 4006d9: 48 89 c7 mov rdi,rax 4006dc: b8 00 00 00 00 mov eax,0x0 4006e1: e8 d2 fc ff ff call 4003b8 <printf@plt> printf("sizeof(func(&n)) = %zu\n", sizeof(func(&n))); 4006e6: b8 1d 09 40 00 mov eax,0x40091d 4006eb: be 04 00 00 00 mov esi,0x4 4006f0: 48 89 c7 mov rdi,rax 4006f3: b8 00 00 00 00 mov eax,0x0 4006f8: e8 bb fc ff ff call 4003b8 <printf@plt> printf("n = %d\n", n); 4006fd: 8b 55 ec mov edx,DWORD PTR [rbp-0x14] 400700: b8 96 08 40 00 mov eax,0x400896 400705: 89 d6 mov esi,edx 400707: 48 89 c7 mov rdi,rax 40070a: b8 00 00 00 00 mov eax,0x0 40070f: e8 a4 fc ff ff call 4003b8 <printf@plt> return 0; 400714: b8 00 00 00 00 mov eax,0x0 } 400719: c9 leave 40071a: c3 ret 40071b: 90 nop 40071c: 90 nop 40071d: 90 nop 40071e: 90 nop 40071f: 90 nop
結果
sizeof("") = 1 sizeof("ABCDE") = 6 sizeof(buf) = 6 sizeof('A') = 4 sizeof(node) = 8 i = 0 sizeof(i++) = 4 i = 0 n = 0 sizeof(int [n++]) = 0 n = 1 n = 1 sizeof(int [++n]) = 8 n = 2 sizeof(c) = 1 sizeof(c+0) = 4 sizeof(main) = 1 sizeof(&main) = 8 n = 2 sizeof(func(&n)) = 4 n = 2
文字列リテラル・char型配列
sizeof("") = 1 sizeof("ABCDE") = 6 sizeof(buf) = 6
文字列リテラルやchar型配列変数というと、式中ではchar型配列における先頭要素へのポインタに展開されますが、sizeof演算子のオペランドにおいては、char型配列として評価されます。末尾のナル文字を含めたサイズが返されます。なので空文字列は「1」。
文字定数
sizeof('A') = 4
sizeof(char)は「1」ですが、文字定数はint型なので「4」。
構造体
4 typedef struct node_t{ 5 unsigned char f; 6 int id; 7 }node_t, node;
sizeof(node) = 8
上記の場合、境界調整でパディングが入るため、5でなくて「8」です。
sizeofオペランドの式
i = 0 sizeof(i++) = 4 …(1) i = 0 n = 0 sizeof(int [n++]) = 0 …(2) n = 1 n = 1 sizeof(int [++n]) = 8 …(3) n = 2
C99以前の規格Cではsizeof演算子のオペランドには整数定数式を要求しますが、可変長配列が導入されたC99では、sizeofのオペランドに可変長配列が指定された場合、以下の作用があるようです。
sizeofの式に可変長配列型の名前が現れ、その配列の要素数がsizeof式の値に影響する場合、その配列の 要素数式は(副作用も含めて)完全に計算される。配列の要素数がsizeofの結果に影響しない場合、その 要素数式が計算されるかどうかは未定義である。 (「Cリファレンスマニュアル 第5版」p244より)
つまり、オペランドに可変長配列の要素数でない式が指定された場合(1)、その式は評価されません。オペランドが可変長配列の要素数であり、かつsizeof式の値に影響する場合(2)(3)、その式は評価されます。(2)のn++は評価されますが、後置インクリメントであるため、sizeof式の評価後にインクリメントしています。そのため、sizeof式はインクリメント前の値で配列要素数が計算されています。(3)の++nは前置インクリメントであるため、インクリメント後の値で配列要素数が計算されています。
暗黙の型変換
sizeof(c) = 1 …(1) sizeof(c+0) = 4 …(2)
(1)char型変数のsizeofは「1」です。
(2)c+0の場合、リテラル数0がint型であるため、式c+0はint型としてサイズ計算されます。
関数
sizeof(main) = 1 (1) sizeof(&main) = 8 (2) n = 2 sizeof(func(&n)) = 4 (3) n = 2
(1)関数名を指定した場合、「1」を返しますが、これはmeaninglessな値だそうです。sizeofで関数へのポインタ値のサイズを取得する場合、「&」をつけます。
(2)検証機は64bit OSであるため、関数へのポインタ値は「8」となります。
(3)関数funcは、ポインタで渡されたnの格納値をインクリメントする定義としていますが、nの値が更新されていないため、関数内の処理は実行されていないようです。
標準出力バッファリング
例えば、1秒ごとに「.」(ドット)を表示するプログレスバー的なものを考えてみます。
1 /* progress.c */ 2 #include <stdio.h> 3 #include <unistd.h> 4 5 int main(int argc, char *argv[]) 6 { 7 int i; 8 9 for(i=0; i<10; i++){ 10 printf("."); 11 sleep(1); 12 } 13 14 return 0; 15 }
これをコンパイルして実行すると、、、
$ ./progress (この間、10秒経過) ..........$
プログラム起動から10秒経過後、終了と同時にプログレスバーがドンと表示されました。
このprintf(3)ですが、引数で渡された文字列を、呼び出されたタイミングでリアルタイムに出力しているわけではなく、いったんライブラリ内部のストリームバッファに溜め込んでおき、あるタイミングが来た時に、一気にバッファの内容を書き出します。あるタイミングとは、
- 改行コードを含む文字列がバッファに書き込まれた
- バッファが一杯になった
- プログラムが終了した
上記イベントのうち、いずれかが最初に発生したタイミングで出力されます。なので、改行コードが書き込まれる前にバッファが一杯になった場合、その時点で出力されます。
改行コードを含む文字列がバッファに書き込まれた場合ですが、例えば「printf(”aaa\nbbb”)」を呼出した場合、「”aaa\n”」が出力されます。なので正確には「バッファの先頭から最初の改行コードまでを出力する」です。
リアルタイムで出力するには、printf(3)を呼び出す以前にバッファリングを切っておくか、
setvbuf(STDOUT, NULL, _IONBUF, 0);
printf(3)の直後にフラッシュするか、
fflush(STDOUT);
あるいはstdbuf(1)コマンドで以下のように実行することで、printf(3)呼出しのタイミングでバッファの内容が吐き出されます。
$ stdbuf -o0 <command>
csvの空行・コメント行・コメント部分を除外して、配列に格納する
CSVファイルのコメント行・空行をスキップし、コメント部を削除して、各レコードを配列に格納するスクリプトです。
・行"aaa,bbb ccc,ddd"を分割して、2列目の値「bbb ccc」がスペースで区切られないこと。←これは対応したが
・"aaa,,ccc"で、「ccc」が3列目の値として取得できること。←これはまだ
#!/bin/ksh typeset -r INFILE="./sample.csv" typeset -r DLMTR="," # # Read Table Data # awk -F${DLMTR} '/^[\x20\t]*($|#.*$)/ {next} {sub("#.*", "") } {print $0}' $INFILE | \ while read LINE do # # Split Line To Array # LINE=`echo ${LINE} | tr -s " " "#"` LINE=`echo ${LINE} | tr -s $DLMTR " "` LINE=(${LINE}) for((i=0; i<${#LINE[@]}; i++)) do LINE[$i]=`echo ${LINE[$i]} | tr "#" " "` done # # Main Proc # echo -n "num of elment is "${#LINE[@]} echo ${LINE[@]} # # Volatile Read Buffer # unset LINE done
csvの空行・コメント行・コメント部分を除外したフィールド数をカウントする
csvファイル(sample.csv)から1行ずつ読み込み、そのフィールド数を標準出力する。
- 行頭の直後が行末である行は空行とする。
- (半角スペースまたはタブ文字)のみで構成された行は空行とする。
- 行頭が「#」の行はコメント行とする。
- 行頭から、最初に出現する「#」直前までの部分文字列が、半角スペースまたはタブ文字のみで構成される場合、その行をコメント行とする。
- (空行またはコメント行)以外の行を有効行とする。
- 有効行に「#」が含まれる場合、行頭から最初に出現する「#」の直前までを当該行の有効文字列とする。
- 有効行に「#」が含まれない場合、当該行全体を有効文字列とする。
- 有効行について、有効文字列をカンマ区切りした場合のフィールド数を標準出力する。
#!/bin/bash for COLNUM in `awk -F"," '/^([ \t]*$|[ \t]*#|$)/ {next} {sub("#.*", "")}{print NF}' ./sample.csv` do echo $COLNUM done
シェルにおける終了コードの扱い
シェルで扱うコマンドの終了コードは、その指定可能な値の範囲は8ビットの整数である。終了コードは、特殊変数「$?」にセットされる。
負の整数を指定することも可能だが、その場合、当該値を2の補数表現として解釈した値が「$?」にセットされる。その場合の「$?」は、当該値を符号無し8ビット整数として表現した値として解釈される。例えば、-1→255、-128→128など。
8ビットで表現可能な値より大きい整数が指定された場合、当該値を下位8ビットに切り捨てた値(正確には、8ビット符号無し整数で表現可能な最大値+1を法とする剰余)が「$?」にセットされる。例えば、256→0、-129→127など。
exit n | hex | echo $? |
---|---|---|
4294967295 | 0xFFFF FFFF | 255 |
2147483648 | 0x8000 0000 | 0 |
2147483647 | 0x7FFF FFFF | 255 |
256 | 0x0000 0100 | 0 |
-1 | 0xFFFF FFFF | 255 |
-128 | 0xFFFF FF80 | 128 |
-129 | 0xFFFF FF7F | 127 |
実用上、「プログラムやシェルスクリプトの終了コードには、0〜255の値を指定できる」と覚えるとよい。