惟以一人治天下. 豈為天下奉一人
c 語言 3 + 5 時, 3, 5 是什麼 type, 3+5 計算之後又是什麼 type? 規格書有定義, 不過有個簡單的方法可以查看, 來看看 gcc 編譯時, printf 秀出的資訊。
測試環境 linux 64 bit:
list 1. a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%llu\n", 3);
6 return 0;
7 }
m
1 descent@u64:~$ gcc -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘int’ [-Wformat=]
4 printf("%llu\n", 3);
5 ~~~^
6 %u
我用 %llu 去印, gcc 發出警告, 說第二個參數是 int 不是 long long unsigned int。所以 3 是 int。再來看一個有趣的例子。
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%lld\n", 0x80000000);
6 return 0;
7 }
m1
1 descent@u64:~$ gcc -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4 printf("%lld\n", 0x80000000);
5 ~~~^
6 %d
0x80000000 是 unsigned int
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%lld\n", 1 << 31);
6 return 0;
7 }
m2
1 a1.c: In function ‘main’:
2 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘int’ [-Wformat=]
3 printf("%lld\n", 1 << 31);
4 ~~~^
5 %d
1 << 31 的 type 是 int, 1 << 31 「看起來」很像是 0x80000000, 但是 type 是 int, list 1 看到 3 是 int, 所以可以推測 1 是 int, 31 是 int, 1 << 31 之後的結果也是 int, 「但是」 1 << 31 是一個 undefined behavior,
ref N1570 p95 第4點
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with
zeros. If E1 has an unsigned type, the value of the result is E1 × 2E2, reduced modulo
one more than the maximum value representable in the result type. If E1 has a signed
type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is
the resulting value; otherwise, the behavior is undefined.
英文不太好看, 大概是這樣的意思, 1 << 31 是 0x80000000 int 無法存入這個數值, 所以是 undefined behavior, unsigned int 才能存 0x80000000。
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%lld\n", 0xffffffff);
6 return 0;
7 }
m3
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4 printf("%lld\n", 0xffffffff);
5 ~~~^
6 %d
使用 -m32 是因為 long 在 linux 64/32 會有不同的解釋, 先用 linux 32 環境為主來觀察。0xffffffff 是 unsigned int, 如果 0xffffffff + 1 之後, type 會是 long long unsigned int 或是 long long int 嗎?
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%lld\n", 0xffffffff + 1);
6 return 0;
7 }
m5
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4 printf("%lld\n", 0xffffffff + 1);
5 ~~~^
6 %d
0xffffffff + 1 還是 unsigned int, 所以會發生什麼事情? unsigned int 裝不下 0xffffffff + 1, 所以計算的數值有會問題,
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%d\n", 0xffffffff + 1llu);
6 return 0;
7 }
m6
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=]
4 printf("%d\n", 0xffffffff + 1llu);
5 ~^
6 %lld
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%d\n", 4294967296);
6
7 return 0;
8 }
而用了 0xffffffff + 1llu, 整個計算結果就變成 long long unsigned int, 就可以完整存下計算結果了, 詳細過程是怎麼樣呢? 可能是 Usual Arithmetic Conversion 描述的那樣, 有個很複雜的規則, 我不確定是怎麼套用。
m7
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=]
4 printf("%d\n", 4294967296);
5 ~^
6 %lld
4294967296 是 long long int
a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5 printf("%d\n", 4294967296 + 0xfffffffflu);
6
7 return 0;
8 }
m8
1 a1.c: In function ‘main’:
2 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=]
3 printf("%d\n", 4294967296 + 0xfffffffflu);
4 ~^
5 %lld
4294967296 + 0xfffffffflu 用 long long int 就可以存, 所以相加後的 type 是 long long int。
想查閱 C 規格書的話在 N1570 6.4.4.1 integer constants。
數值計算規則非常複雜, 並不是只有這樣, C 規格書定義了複雜的規則。
ref:
C 語言的 usual arithmetic conversion
c 語言中的 0x7fffffff 和 0xffff0000 是什麼 type?
Signed integer 在 overflow 時能被 undefined behavior sanitizer 偵測到, 但 unsigned 則否, 也可以做為 type 選擇時的考量
回覆刪除感謝分享, 忘記還有 sanitizer 這個武器。
回覆刪除