2022年3月13日 星期日

printf 的 integer promotion

巧者勞力, 智者勞心
p.c
 1 #include <stdio.h>
 2
 3 int main(int argc, char *argv[])
 4 {
 5   unsigned char a=0x80;
 6   signed char b=0x80;
 7
 8   printf("unsigned char a: %#x, signed char b: %#x\n", a, b);
 9   printf("unsigned char a: %#x (%d), signed char b: %hhx (%hhd)\n", a, a, b, b);
10   return 0;
11 }

list 1 執行結果
 1 unsigned char a: 0x80, signed char b: 0xffffff80
 2 unsigned char a: 0x80 (128), signed char b: 80 (-128)


list 1 是執行結果, L1 顯示了 signed char b: 0xffffff80, 不是預期的 0x80, 有點奇怪是吧? 引用之前的文章 - compiler [7] - code generator - funcall call, pass argument
function 參數的傳遞比想像中複雜, 當 function 沒有 prototype 時或是使用 K&R style 的宣告或是 ... 這種參數 - ex: printf(const char *format, ...), 會發動 integer promtion, 這很好理解, 可以參考《“对于那些没有原型的函数,传递给函数的实参将进行缺省参数提升”是什么意思?
printf 的參數是 ... 所以會發動 integer promtion, integer promtion 之後, signed char b 0x80 的 -128 變成了 4bytes 的 -128, 也就是 0xffffff80, 這便是 %#x 印出 0xffffff80 的原因。

cpu 用什麼指令做這件事情, 反組譯一下觀察, list 2 L716, 717 的指令 movsbl, movzbl 就是在做這件事情。由於比較熟悉 x86-32, 所以編譯成 x86-32 來觀察, 其他平台應該也有類似的指令, 就不一一觀察了。

list 2. p.c.dis 編譯指令 gcc -static -no-pie -fno-pic -m32 -g p.c -o p
     1 
     2 p:     file format elf32-i386
   705 08049815 <main>:
   706  8049815:	8d 4c 24 04          	lea    0x4(%esp),%ecx
   707  8049819:	83 e4 f0             	and    $0xfffffff0,%esp
   708  804981c:	ff 71 fc             	push   -0x4(%ecx)
   709  804981f:	55                   	push   %ebp
   710  8049820:	89 e5                	mov    %esp,%ebp
   711  8049822:	53                   	push   %ebx
   712  8049823:	51                   	push   %ecx
   713  8049824:	83 ec 10             	sub    $0x10,%esp
   714  8049827:	c6 45 f7 80          	movb   $0x80,-0x9(%ebp) # unsigned char a=0x80
715 804982b: c6 45 f6 80 movb $0x80,-0xa(%ebp) # signed char b=0x80
716 804982f: 0f be 55 f6 movsbl -0xa(%ebp),%edx # signed char b=0x80 717 8049833: 0f b6 45 f7 movzbl -0x9(%ebp),%eax # unsigned char a=0x80 718 8049837: 83 ec 04 sub $0x4,%esp 719 804983a: 52 push %edx 720 804983b: 50 push %eax 721 804983c: 68 08 30 0b 08 push $0x80b3008 722 8049841: e8 da 85 00 00 call 8051e20 <_IO_printf> 723 8049846: 83 c4 10 add $0x10,%esp 724 8049849: 0f be 5d f6 movsbl -0xa(%ebp),%ebx 725 804984d: 0f be 4d f6 movsbl -0xa(%ebp),%ecx 726 8049851: 0f b6 55 f7 movzbl -0x9(%ebp),%edx 727 8049855: 0f b6 45 f7 movzbl -0x9(%ebp),%eax 728 8049859: 83 ec 0c sub $0xc,%esp 729 804985c: 53 push %ebx 730 804985d: 51 push %ecx 731 804985e: 52 push %edx 732 804985f: 50 push %eax 733 8049860: 68 34 30 0b 08 push $0x80b3034 734 8049865: e8 b6 85 00 00 call 8051e20 <_IO_printf> 735 804986a: 83 c4 20 add $0x20,%esp 736 804986d: b8 00 00 00 00 mov $0x0,%eax 737 8049872: 8d 65 f8 lea -0x8(%ebp),%esp 738 8049875: 59 pop %ecx 739 8049876: 5b pop %ebx 740 8049877: 5d pop %ebp 741 8049878: 8d 61 fc lea -0x4(%ecx),%esp 742 804987b: c3 ret

另外 printf 提供了 %hh 來印出 signed char 或是 unsigned char 變數。

ref:

沒有留言:

張貼留言

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

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