现在的位置: 首页 > 综合 > 正文

照妖镜和火眼金睛

2019年03月10日 ⁄ 综合 ⁄ 共 1435字 ⁄ 字号 评论关闭

照妖镜和火眼金睛

  如果在 linux 下编写 C 程序,那么你将获得两个犀利的法宝:


照妖镜

  一个C程序(max.c):

#define MAX(a,b) ((a)>=(b)?(a):(b))

int main(){
    int c=MAX(1,2); // 注注注注释
    return 0;
}

  程序很简单,就是定义和使用一个MAX宏,宏在正式编译前是会被替换为本来面目的,我们现在看到的不是它的真身。让我们用照妖镜来照照:

gcc -E -o max2.c max.c

  这里的 -o max2.c 是让 gcc 把要输出东西输出到 max2.c 文件中

  妖怪!快快现形吧:

# 1 "max.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "max.c"


int main(){
 int c=((1)>=(2)?(1):(2));
 return 0;
}

  上面就是max2.c中的内容,MAX(1,2) 被替换成了 ((1)>=(2)?(1):(2)),这只孽畜终于现形了!

  照妖镜的作用就是替换宏,但是宏好像大家都不太用。不过宏在 现代 linux 内核源代码中简直是运用到了极致,甚至可以说 linux 内核是由 C、宏、汇编 写出来的。宏是可以嵌套的,也就是说宏的 参数 或 右部 中还可以出现能够被替换的宏,所以情况就相当复杂了——十个字符的简单的一条语句,当被还原为本来面目时,可能就变成七八十个字符了,要分析这样的语句,照妖镜就大显神威了。

  关于宏,后面会独立出一篇来介绍。


火眼金睛

  照妖镜应该是不如火眼金睛的,火眼金睛可以看到及其微小的细节。下面我写了个Hello World (hello.c):

#include <stdio.h>

int main(){
    printf("Hello, World!\n");
    return 0;
}

  Hello World 就不用解释了吧,鼎鼎有名啊!O(∩_∩)O~,然后我们用火眼金睛来看一下:

gcc -S -o hello.s hello.c

  Hello World 的汇编版就出来了(hello.s):

    .file   "hello.c"
    .section    .rodata
.LC0:
    .string "Hello, World!"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, (%esp)
    call    puts
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)"
    .section    .note.GNU-stack,"",@progbits

  其内容我们后面再慢慢分析,现在只要知道怎么用“火眼金睛”就行了,接下来的几篇都得靠悟空了。


  照妖镜和火眼金睛其实都是靠截断编译过程得到中间产物的,gcc的完整编译过程是:

预处理->编译->汇编->链接

  使用不同的编译选项可以得出不同的中间产物:

编译阶段 命令 截断后的产物
    C源程序
预处理 gcc -E 替换了宏的C源程序(没有了#define,#include…), 删除了注释
编译 gcc -S 汇编源程序
汇编 gcc -c 目标文件,二进制文件, 允许有不在此文件中的外部变量、函数
链接 gcc 可执行程序,一般由多个目标文件或库链接而成, 二进制文件,所有变量、函数都必须找得到

  也许有同学发现了 -c 我还没讲呢!二进制文件的分析后面也有用到,但是很少,用到的时候再说吧。

抱歉!评论已关闭.