一些关于c的陷阱(Some of the pitfalls)
This project is maintained by wangfakang
c陷阱(不要只看静态的代码,执行过后你会崩溃的)
这篇文章是与c相关的第一篇文章,之所以要放在第一篇--或许你看看标题应该明白了我的意思了,在我们以后写c的过程中
不要让子在奔溃,起码在这一点上,好了,下面就转正文吧:
来看看下面几行简单的c代码:
## test1.c:
1 #include <stdio.h>
2
3 int main()
4 {
5 const int a = 10;
6 //int g[a];
7 int *p = (int*) &a;
8 *p = 100;
9 int b = a;
10
11 printf("%d\t%d\t%d\n", a, *p, b);
12
13 return 0;
14
15 }
想想输出结果是什么?
看到这估计你应该有自己的结果了.
我使用的gcc版本是:
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04)
当我执行:
gcc test1.c 时生成a.out 执行./a.out
结果是:
100 100 100
当我执行:
gcc -O2 test1.c 时生成a.out 执行./a.out
结果是:
100 100 10
这个时候肯定有人要问这是怎么回事恩? 为何输出的b是10而不是100?
搞清楚这个问题首先我们看点其他的知识点:
一.编译优化,由于默认状态下gcc 使用的优化级别是O1,其中优化级别排序是:
O0 -->> O1 -->> O2 -->> O3
-O0表示没有优化,-O1为缺省值,-O3优化级别最高
二.计算机寻址的方式
寄存器寻址
存储器寻址
然后再来看看这几个问题:
刚才默认状态下输出都是100,当提高了优化级别的时候最后b是10.其实当提高了编译优化参数的时候
此时编译器对其"const int a = 10;"做了优化,由于是const修饰了变量a,所以最终寻址a的时候
就是用立即数寻址方式.此时语句"*p = 100;"虽然把变量a中的内存修改了但是下面语句"int b = a;
被编译器优化了,此时使用的立即数的方式,不会再从内存中读取变量a.
注意:
当我们在使用"volatile"关键字的时候,此时结果又会不一样的.volatile告诉编译器每次访问的时候必须
去从内存中去读取.
下面再看看二者在汇编上的差异:
优化前:
10 .cfi_startproc
11 pushq %rbp
12 .cfi_def_cfa_offset 16
13 .cfi_offset 6, -16
14 movq %rsp, %rbp
15 .cfi_def_cfa_register 6
16 subq $16, %rsp
17 movl $10, -16(%rbp)
18 leaq -16(%rbp), %rax
19 movq %rax, -8(%rbp)
20 movq -8(%rbp), %rax
21 movl $100, (%rax)
22 movl -16(%rbp), %eax
23 movl %eax, -12(%rbp)
24 movq -8(%rbp), %rax
25 movl (%rax), %edx
26 movl -16(%rbp), %eax
27 movl -12(%rbp), %ecx
28 movl %eax, %esi
29 movl $.LC0, %edi
30 movl $0, %eax
31 call printf
32 movl $0, %eax
33 leave
34 .cfi_def_cfa 7, 8
35 ret
36 .cfi_endproc
优化后:
11 .cfi_startproc
12 subq $8, %rsp
13 .cfi_def_cfa_offset 16
14 movl $10, %r8d
15 movl $100, %ecx
16 movl $100, %edx
17 movl $.LC0, %esi
18 movl $1, %edi
19 xorl %eax, %eax
20 call __printf_chk
21 xorl %eax, %eax
22 addq $8, %rsp
23 .cfi_def_cfa_offset 8
24 ret
25 .cfi_endproc
所以在写代码地的时候一定要想清楚了在写.
下面在看一个列子:
test2.c
1
2 #include<stdio.h>
3
4
5 int main()
6 {
7 int a=12;
8 float b = 12.99;
9 float c = 12.25;
10
11
12 printf("%d\t%d\t%f\n",a, b, c);
13
14 return 0;
15 }
看清楚printf函数中给的格式符号:有人可能会说b是12 c是12.25.
其实并不是这样的.
所以最基础printf函数的实现原理你知道嘛?
在这个过程中其实是这样的参数cba从右边依次压栈,然后看到"%d"格式符则取出四字节,
看大%f按照8字节来做解析(在printf函数中float型都会转化为double来处理所以8bit).
test3.c
i++ 与 ++i的区别:
最基本的都知道.但是真正的区别又是什么恩?
i++ = 3;//编译不通过
++i = 4;
第一种为啥编译不通过恩,其实i++底层是这样做的,i++会生成中间一个临时常量
此行相当于向常量赋值.
test4.c
1
2 #include <stdio.h>
3
4 int main()
5 {
6 unsigned int a = 1;
7 short b = -1;
8 printf("%c\n",a > b ? 'a':'b');
9
10 return 0;
11 }
代码逻辑很简单,但是输出的值你可以看看是a or b; 结果是b.
原因原因也很简单,在这里a和b进行关系运算的时候,由于b是有
符号的,a是无符号的,此时有符号会向无符号转换.-1最高为是1
了,所以会很大.规则:短字节转化为长字节时有符号位的用符号
位填充.
基于此类别其实还有很多,其实有时候很简单的一句代码,但是底层却有很多好玩的.
在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流