C puzzles详解【13-15题】
第十三题
int CountBits(unsigned int x) { int count=0; while(x) { count++; x = x&(x-1); } return count; }
知识点讲解
- 位运算
关于位运算的一些例子参考:
http://www.ugcs.caltech.edu/~wnoise/base2.html
题目讲解
x&(x-1)常见的两种应用:
1)计算x二进制形式中1的个数,每循环一次,将x二进制形式最右边的1变成0;
2)判断x是否是2的幂,若x&(x-1)==0,x为2的幂。
第十四题
Are the following two function prototypes same? int foobar(void); int foobar(); The following programs should be of some help in finding the answer: (Compile and run both the programs and see what happens) Program 1: #include <stdio.h> void foobar1(void) { printf("In foobar1\n"); } void foobar2() { printf("In foobar2\n"); } int main() { char ch = 'a'; foobar1(); foobar2(33, ch); return 0; } Program 2: #include <stdio.h> void foobar1(void) { printf("In foobar1\n"); } void foobar2() { printf("In foobar2\n"); } int main() { char ch = 'a'; foobar1(33, ch); foobar2(); return 0; }
知识点讲解
- foo()和foo(void)是不一样的
调用foo()时,可以传入任意个数,任意类型的参数,编译器不会关心传给foo的参数,传入的参数对foo函数的执行没有影响;
调用foo(void)时,不可传入参数,否则编译不通过;
void foo() {}是定义无参数函数的一种过时的用法,故日常编写代码时,若函数没有参数,最好写成foo(void)的形式。
题目讲解
Program 1能编译通过,运行后正常打印两个字符串;
Program 2编译不通过,提示foobar1参数过多。
第十五题
What's the output of the following program and why? #include <stdio.h> int main() { float a = 12.5; printf("%d\n", a); printf("%d\n", *(int *)&a); return 0; }
知识点讲解
- printf中的%f读取sizeof(double)个字节;
#include <stdio.h> int main() { int a = 10, b = 20, c = 30; printf("%f, %d\n", a, b, c); return 0; }
输出:0.000000, 30
调用printf时,双引号后面的所有参数从右往左依次入栈,然后将栈中的数据根据双引号中的格式从低地址向高地址依次读取并打印出来。
上述代码调用printf时,c,b,a依次入栈,%f读取sizeof(double)=8个字节,
即a,b所占的区域,转换成浮点数为0.000000,%d继续读取下面4个字节,即c所占的区域,转换为整型数为30。
- printf中传入的float型参数自动转换成double型;
#include <stdio.h> int main() { float a = 1.2; int b = 10; printf("%x, %x, %d\n", a, b); return 0; }
输出:40000000, 3ff33333, 10
调用printf时,参数a被转为double型后再传入printf,printf的前两个%x读取的是a所占的8个字节,%d读取的是b所占的4个字节。
gcc –S test.c命令可以生成汇编代码,上例的汇编代码主体如下:
main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp movl $0x3f99999a, %eax movl %eax, 28(%esp) movl $10, 24(%esp) flds 28(%esp) movl $.LC1, %eax movl 24(%esp), %edx movl %edx, 12(%esp) fstpl 4(%esp) movl %eax, (%esp) call printf movl $0, %eax leave ret
“flds 28(%esp)”,flds将28(%esp)地址处的单精度浮点数即0x3f99999a加载到浮点寄存器,“fstpl 4(%esp)”即将浮点寄存器中的浮点数转换成double型后放到4(%esp)处。
- printf中的%c读取4个字节,printf的char型参数先转换为int再入栈;
#include <stdio.h> int main() { char a = 97; int b = 20; printf("%c, %d\n", a, b); return 0; }
输出:a, 20
上述c代码的汇编代码主体为:
main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp movb $97, 31(%esp) movl $20, 24(%esp) movsbl 31(%esp), %edx movl $.LC0, %eax movl 24(%esp), %ecx movl %ecx, 8(%esp) movl %edx, 4(%esp) movl %eax, (%esp) call printf movl $0, %eax leave ret
“movl %edx, 4(%esp)”在栈中压了4个字节。
- sizeof(‘a’)=4;
#include <stdio.h> int main() { char a = 'a'; printf("%d, %d\n", sizeof(a), sizeof('a')); return 0; }
sizeof求类型的大小,sizeof(a)即sizeof(char),sizeof(‘a’)即sizeof(int),‘a’自动转换为int型。
题目讲解
“printf("%d\n", a);”,a转换为double型再入栈,%d读取double型数据的低4个字节;
“printf("%d\n", *(int *)&a);”float占4个字节,*(int *)&a将a在内存中存放值转换为整型数后再传入printf。
浮点数在内存中如何表示参考第九题讲解。