2022年10月21日 星期五

c 語言數值運算的 type

惟以一人治天下. 豈為天下奉一人
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:
  1. C 語言的 usual arithmetic conversion
  2. c 語言中的 0x7fffffff 和 0xffff0000 是什麼 type?

2 則留言:

  1. Signed integer 在 overflow 時能被 undefined behavior sanitizer 偵測到, 但 unsigned 則否, 也可以做為 type 選擇時的考量

    回覆刪除
  2. 感謝分享, 忘記還有 sanitizer 這個武器。

    回覆刪除

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。