gcc: 基本操作(1)

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

预处理

调用cpp做宏替换, 展开include文件, 把 .c文件 转 .i文件

gcc -E a.c-o a.i

编译

调用ccl, 把 .i文件 转 .s 汇编代码

gcc -S a.i -o a.s

汇编

调用as, 把.s文件 转 .o 二进制机械指令

gcc -c a.s -o a.o

链接

调用ld,把.o文件和调用的库代码链接为 .out可执行文件

gcc a.o -o a.out

实践

文件2main.c内容

#include <stdio.h>
extern int max(int, int);
int main()
{
   printf("x > y ? %d", max(20,3));
   return 0;
}

文件2.c内容

int max(int x, int y)
{
    return x > y ? 1 : 0;
}

编译 2main.c 得到目标文件 2main.o

[root@localhost ~]# vi 2main.c
[root@localhost ~]# gcc -E 2main.c -o 2main.i
[root@localhost ~]# gcc -S 2main.i -o 2main.s
[root@localhost ~]# gcc -c 2main.s -o 2main.o

编译 2.c 得到目标文件 2.o

[root@localhost ~]# gcc -E 2.c -o 2.i
[root@localhost ~]# gcc -S 2.i -o 2.s
[root@localhost ~]# gcc -c 2.s -o 2.o

链接两个目标文件

[root@localhost ~]# gcc 2.o 2main.o -o 2main.out

执行结果

[root@localhost ~]# ./2main.out
x > y ? 1

快速编译链接

gcc 2.c 2main.c -o 2main.out

[root@localhost ~]# gcc 2.c 2main.c -o 3main.out
[root@localhost ~]# ./3main.out
x > y ? 1

检错

查看报错

-pedantic 检查被编译代码是否符合ANSI/ISO C标准

-Wall 产生警告信息

-Werror 警告信息当作错误来中断编译

语言标准

-ansi 等价于C的-std=c90. C++的-std=c++98.

-std=以下选项之一

`c90'

`c89'

`iso9899:1990'

`c90'

`c89'

`iso9899:1990' 所有ISO C90程序,C代码时等价于-ansi,

`iso9899:199409' ISO C90 修订版1

`c99' 不一定完全支持

`c11' 特性没确定好

`gnu90'

`gnu89' (C的默认选项)ISO C90和一部分C99特性,等价于c90

`gnu99' (下一个默认选项, 当C99完全支持时)ISO C99,等价于c99

`gnu11'

`c++98' 1998 ISO C++标准, C++代码时等价于-ansi

`gnu++98' (C++默认选项) 等同于c++98

`c++11'

`gnu++11'

c++1y 下一个新版本

c++14

编译器优化选项

-O0 不优化

-O1 至 -O3 优化级别加强

-Os 优化程序尺寸, 打开了大部分O2优化中不会增加程序大小的优化选项。关闭了通常不需要的优化选项:

-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -fprefetch-loop-arrays

O0选项不进行任何优化,在这种情况下,编译器尽量的缩短编译消耗(时间,空间),此时,debug会产出和程序预期的结果。当程序运行被断点打断,此时程序内的各种声明是独立的,我们可以任意的给变量赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中 精确地获取你期待的结果.

O1优化会消耗少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。

O2会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。

O3在O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。

Os主要是对代码大小的优化,我们基本不用做更多的关心。 通常各种优化都会打乱程序的结构,让调试工作变得无从着手。并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序的正确性。

优化代码有可能带来的问题 ?

1.调试问题:任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内load/store操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。

2.内存操作顺序改变所带来的问题:在O2优化后,编译器会对影响内存操作的执行顺序。例如:-fschedule-insns允许数据处理时先完成其他的指令;-fforce-mem有可能导致内存与寄存器之间的数据产生类似脏数据的不一致等。对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化。例如,采用volatile关键字限制变量的操作方式,或者利用barrier迫使cpu严格按照指令序执行的。

动态库链接兼容

同时有C和C++两种文件, -lstdc++ 指定用libstdc++.so这个动态库, 省略"lib"和".so"则是"stdc++";

gcc 1.cpp -o 1.out -Wall -lstdc++ -std=c++14 -O3

静态库

生成

ar crv libmylib.a add.o sub.o mul.o div.o logger.o

返显:

a - add.o

a - sub.o

a - mul.o

a - div.o

a - logger.o

ar 命令: 归档目标文件,生成静态库

参数 c : 如果需要生成新的库文件, 不要警告

参数 r : rewrite代替库中现有的文件或插入新的文件

参数 v : 输出详细信息

ar t limylib.a 查看静态库 libmylib.a 中包含的目标文件

ar --help

链接

gcc test.c -L/home/SourceCode -lMath -static -o test

在/home/SourceCode搜索库文件libMath.a(默认是动态库优先, -static指定静态库)

静态库链接时搜索路径顺序:

  1. ld会去找GCC命令中的参数-L
  2. 再找环境变量LIBRARY_PATH 指定的gcc静态链接库文件搜索路径
  3. 最后找当初compile gcc时写在程序内的目录 /lib, /usr/lib, /usr/local/lib

动态库

gcc -fPIC -c math.c -o math.o 生成目标文件

gcc -shared math.o -o libMath.so 生成动态库

gcc -fPIC -shared math.c -o libMath.so 一行快速生成动态库

要求:运行期 export LD_LIBRARY_PATH=path指定环境变量,否则找不到动态库的位置

动态链接时、执行时搜索路径顺序:

  1. 编译目标代码时指定的动态库搜索路径
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
  4. 默认的动态库搜索路径/lib
  5. 默认的动态库搜索路径/usr/lib

生成动态静态库文件的 头文件

vi my_lib.h

#ifndef __MY_LIB_H__
#define __MY_LIB_H__

int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
void logger(const char *);//fprintf

#endif

静态库实践

vi add.c

int add(int x, int y)
{
    return x + y;
}

vi sub.c

int sub(int x, int y)
{
    return x + y;
}

vi mul.c

int mul(int x, int y)
{
    return x + y;
}

vi div.c

int div(int x, int y)
{
    return x + y;
}

vi logger.c

#include <stdio.h>
int logger(const char * str)
{
    fprintf(stdout, "msg= %s\n", str);
}

批量编译生成目标文件

gcc -c add.c sub.c mul.c div.c logger.c

得到

add.o sub.o mul.o div.o logger.o

ar归档:

ar cvr libmylib.a add.o sub.o mul.o div.o logger.o

测试静态库

vi test.c

#include "my_lib.h"

int main(int argc, char **argv)
{
    int c = add(2, 5);
    logger("hello world");
    return 0;
}

编译test.c:

gcc test.c -L. -lmylib -o test.out -Wall -O3 -std=c11

当前目录下链接 libmylib.so 或 libmylib.a

执行

[root@localhost ~]# ./test.out
msg= hello world

查看静态库的目标文件

[root@localhost ~]# ar t libmylib.a
add.o
sub.o
mul.o
div.o
logger.o

makefile自动构建

makefile只是一个构建工具的配置文件

原理:

根据源文件的最后修改时间, 检测哪些*.o目标文件需要重新编译;

根据makefile配置文件, 维护每个目标文件之间的依赖关系, 优先编译(或生成)被依赖的目标文件, 并根据执行命令去生成目标文件;

vi makefile

.PHONY: build test

build2: libmylib.a

libmylib.a: add.o sub.o mul.o div.o logger.o
    ar crv $@ add.o sub.o mul.o div.o logger.o

add.o: add.c
    gcc -c add.c

sub.o: sub.c
    gcc -c sub.c

mul.o: mul.c
    gcc -c mul.c

div.o: div.c
    gcc -c div.c

logger.o: div.c
    gcc -c div.c

test: a.out

a.out: test.c libmylib.a
    gcc test.c -L. -lmylib -o a.out -Wall -O3 -std=c11

效果:

  1. 执行 make build2 会构建 libmylib.a
  2. 执行 make test 将会生成 链接了libmylib.a静态库的 test程序

先删除目标文件, 尝试用make build2 构建命令

[root@localhost ~]# rm -f add.o
[root@localhost ~]# make build2
gcc -c add.c
ar crv libmylib.a add.o sub.o mul.o div.o logger.o
r - add.o
r - sub.o
r - mul.o
r - div.o
r - logger.o

删除静态库文件, 尝试用 make test 构建命令(自动编译被依赖的静态库)

[root@localhost ~]# rm -f libmylib.a
[root@localhost ~]# make test
ar crv libmylib.a add.o sub.o mul.o div.o logger.o
a - add.o
a - sub.o
a - mul.o
a - div.o
a - logger.o
gcc test.c -L. -lmylib -o a.out -Wall -O3 -std=c11
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章