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の値が更新されていないため、関数内の処理は実行されていないようです。

5.1 プロセスID

Linuxではカーネルがinitを起動する際、以下の順番でパス検索し、はじめに見つかったinitを起動する。

  1. /sbin/init
  2. /etc/init
  3. /bin/init
  4. /bin/sh

initのPIDは1。
PIDの最大値は32768(/proc/sys/kernel/pid_max)。
PIDの割当ては単調増加方式で、サイクリックに使用される。
ただし、0〜300のPIDは再利用されない。

GNU Cでは、PIDはpid_t型(typedef int)。
getpid(2)システムコールでPIDを取得する。
getppid(2)システムコールで親プロセスのPIDを取得する。

標準出力バッファリング

例えば、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)ですが、引数で渡された文字列を、呼び出されたタイミングでリアルタイムに出力しているわけではなく、いったんライブラリ内部のストリームバッファに溜め込んでおき、あるタイミングが来た時に、一気にバッファの内容を書き出します。あるタイミングとは、

  1. 改行コードを含む文字列がバッファに書き込まれた
  2. バッファが一杯になった
  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の空行・コメント行・コメント部分を除外したフィールド数をカウントする

awkを使った、テキストファイルを処理するスクリプトです。

csvファイル(sample.csv)から1行ずつ読み込み、そのフィールド数を標準出力する。

  1. 行頭の直後が行末である行は空行とする。
  2. (半角スペースまたはタブ文字)のみで構成された行は空行とする。
  3. 行頭が「#」の行はコメント行とする。
  4. 行頭から、最初に出現する「#」直前までの部分文字列が、半角スペースまたはタブ文字のみで構成される場合、その行をコメント行とする。
  5. (空行またはコメント行)以外の行を有効行とする。
  6. 有効行に「#」が含まれる場合、行頭から最初に出現する「#」の直前までを当該行の有効文字列とする。
  7. 有効行に「#」が含まれない場合、当該行全体を有効文字列とする。
  8. 有効行について、有効文字列をカンマ区切りした場合のフィールド数を標準出力する。
#!/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の値を指定できる」と覚えるとよい。