C0

一些关于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
了,所以会很大.规则:短字节转化为长字节时有符号位的用符号
位填充.

基于此类别其实还有很多,其实有时候很简单的一句代码,但是底层却有很多好玩的.

欢迎一起交流学习

在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

Thx

Author