gcc编译选项


1.gcc编译时指定宏定义

编译时:gcc -DMAX=32 -o a a.c

相当于在文件a.c中定义:#define MAX 32


2.链接静态库的顺序问题

         链接静态库时,如果多个静态库之间存在依赖关系,则有依赖关系的静态库之间存在顺序问题,这个在使用静态库时,需要注意,否则会报符号找不到问题,举例:
       libb.a依赖于是liba.a,而可执行文件test只直接依赖于libb.a,则链接选项应当为:
        -b -a,而不是-a -b,否则会报liba.a中的某些符号找不到. 
 
      gcc -c a.c
      ar cr liba.a a.o
       gcc -c b.c
     ar cr libb.a b.o # 虽然libb.a使用到了liba.o中的一些函数,但并不会将它们的定义包含进来,所以在链接test时需要指定这两个库
 
另外,在编译libb.a时是不指定liba.a的,因为编译一个静态库不会使用到链接选项,而只需要指定需要依赖的头文件路径即可.


3. -Wl的使用

-Wl表示后面的参数传递给链接器,其中l是linker的意思。
"-Wl"表示是传递给链接器ld的参数,而不是编译器gcc/g++的参数
用“-Wl,-Bstatic”指定链接静态库,使用“-Wl,-Bdynamic”指定链接共享库
链接时指定共享库的搜索路径(类似于设置LD_LIBRARY_PATH):
-Wl,-rpath=/usr/local/abc:/data/abc
以上也可以分开写:
-Wl,-rpath=/usr/local/abc -Wl,-rpath=/data/abc
部分库链接它的静态库,部分库链接它的共享库:
-Wl,-static -lb -Wl,-call_shared -la -lz
指定链接器
-Wl,-dynamic-linker /lib/ld-linux.so.2 -e _so_start
指定导出
-Wl,--export-dynamic,--version-script,exports.lds    

4.--start-group

 关于–start-group和 –end-group的含义:
archives)或者—start-group archives –end-group参数中间的目标文件会被ld反复搜索,对相互交叉引用的目标文件很有用,而不是默认的只搜索一次。

所以,我的理解是,如果ld载入了一个库,发现该库中,有UNDF,未被定义的变量,有了这个参数的指示后,就会在这一堆.a和.o文件里面反复搜索,直至找到为止,否则,如果在已经加载的库中,找不到,就会报错。

这个问题的原因是b.cpp依赖a.cpp,gcc要求(实际是ld要求)libb.a须放在liba.a前面,即需要改成:g++ -g -o x x.o libb.a liba.a,也就是被依赖的库需要放在后头
 注: Wl 和--start-group / --end-group 之间不能有空格。

这是最常规的解决办法,除此之外, 只需要加入--start-group--end-group两个链接参数,即可保持被依赖的库放在前头,也就是改成如下即可:g++ -g -o $@  -Wl,--start-group $^ -Wl,--end-group。

所有的连接器命令行选项前必须加上前缀'-Wl'(或者能被特定编译器驱动接受的其他前缀),就像下面这样
  gcc -Wl,--startgroup foo.o bar.o -Wl,--endgroup 

这里的“-Wl,”表示后面跟着的参数是传递给链接器ld的,gcc不关心具体是啥。 “--start-group”表示范围的开始;“--end-group”表示范围的结束,是可选的。位于“--end-group”之后的仍然要求被依赖的库放在后头。注意“--start-group”不能重复,相关链接参数:--whole-archive 和 --no-whole-archive。在编译的时候要用-Wl,[options]来传递给链接器,不然编译器会不认得这个选项
--whole-archive选项解决的是编译中常遇到的问题。 在代码中定义的符号(如函数名)还未使用到之前,链接器并不会把它加入到连接表中。如下面这个例子:
a.cpp:
void func(){printf("I am in a.cpp.\n");}
main.cpp:
extern void func();
int main(){func(); printf("I am in main.cpp"); return 0;}
首先编译g++ -c a.cpp,再打包ar -r liba.a a.o。
如果这么链接g++ -L. -la main.cpp -o main,则链接器会报错,称func()未定义。其实改一下顺序既可以解决g++  main.cpp -L. -la -o main。
或者使用g++ -Wl,--whole-archive -L. -la -Wl,--no-whole-archive main.cpp -o main,将liba.a中的所有.o中的符号都链接进来。

5. wrap选项替换已有库函数

有时候为了定位系统的软件bug或是一些标准库函数无法满足需求,我们往往希望能够实现一个自己定义的接口函数,但是代码那么多,如果将原接口一一替换,将会是一件多么痛苦的事情。
    好在gcc在链接的时候提供了一个比较简单又非常实用的方法:--wrap=symbol,它可以使链接器在链接symbol符号时优先查找__wrap_symbol,查找不到时才会链接原始的symbol符号,更加详细的说明可以参考man ld。


使用ld的wrap选项替换已有库函数,很多时候,可能需要替换已有库函数,或者对其库函数进行修改

为了避免对那些静态链接库或者动态链接库文件大动干戈,我们可以使用ld提供的–wrap选项。

例如,想把所有的malloc函数都作修改,以便让malloc出的内存都是32字节对齐的。
我们可以给ld传选项“­­wrap=malloc”, 告诉ld,我们将替换名称为malloc的函数。
接着再如下定义一个新的malloc函数:

example:

下面通过举一个替换库函数malloc的例子进行说明:
    1. 首先实现一个自定义的__wrap_symbol函数__wrap_malloc:


  1. /********* test.c *********/

  2. void *__wrap_malloc(size_t size)
  3. {
  4.     printf("My malloc function!\n");
  5.     return __real_malloc(size);
  6. }

  7. int main(int argc, char*argv[])
  8. {
  9.     char *p =(char *)malloc(16);
  10.     free(p);
  11.     
  12.     return 0;
  13. }
  
  2. 指定链接时要替换的函数:gcc test.c -o test -Wl, --wrap=malloc,其中-Wl是链接选项。这样当malloc函数调用malloc时就会就替换为__wrap_malloc函数。

    这里需要注意两点:
    1) 当需要调用真正的malloc函数时,需要使用 __real_malloc();
    2) 如果需要指定多个替换函数,需要写明多个链接选项,比如要替换malloc和free,那么编译时需要这么写:gcc test.c -o test  -Wl, --wrap=malloc -Wl, --wrap=free;


void * __wrap_malloc( size_t size) {
  void* result;
  result=memalign(32, size);
  return result;
}

 可以看到,程序中有个类似库函数名称的__wrap_malloc。
ld在遇到__wrap选项后,会使用__wrap_malloc函数名替换所有对malloc的调用。
并以此实现替换的作用。

那麽,如果还向调用原函数怎么办呢?

可以使用__real_malloc,这个函数名称就对应了原来的malloc。
每次调用malloc时都打印生成的指针地址。

void * __wrap_malloc( size_t size) {
  void* result;
  result= __real_malloc( size);
  printf("Malloc: allocate %d byte mem, start at %p", size, result);

  return result;
}

#include <stdio.h>
#include <stdlib.h>

void * __wrap_malloc( size_t size) {
  void* result;
  //result= __real_malloc( size);   result = memalign(32, size);
  printf("Malloc: allocate %d byte mem, start at %p", size, result);

  return result;
}

int main ()
{
  int i,n;
  char * buffer;

  printf ("How long do you want the string? ");
  //scanf ("%d", &i);   i = 200;

  buffer = (char*) malloc (i+1);
  if (buffer==NULL) exit (1);

  for (n=0; n<i; n++)
    buffer[n]=rand()%26+'a';
  buffer[i]='\0';

  printf ("Random string: %s\n",buffer);
  free (buffer);

  return 0;
}

编译选项:

$gcc test_malloc.c -Wl,--wrap=malloc

gcc -I -L -l区别
我们用gcc编译程序时,可能会用到“-I”(大写i),“-L”(大写l),“-l”(小写l)等参数,下面做个记录: 例:
gcc -o hello hello.c -I  /home/hello/include  -L  /home/hello/lib  -lworld
上面这句表示在编译hello.c时:
-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录,寻找的顺序是: /home/hello/include-->/usr/include-->/usr/local/include
-L /home/hello/lib表示将/home/hello/lib目录作为第一个寻找库文件的目录,寻找的顺序是: /home/hello/lib-->/lib-->/usr/lib-->/usr/local/lib
 -lworld表示在上面的lib的路径中寻找libworld.so动态库文件(如果gcc编译选项中加入了“-static”表示寻找libworld.a静态库文件)



编译成可执行文件
首先我们要进行编译test.c为目标文件,这个时候需要执行
gcc –c –I /usr/dev/mysql/include test.c –o test.o
最后我们把所有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test

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

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

有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

GCC 的 -I 参数可以用来指定头文件目录,当前目录和缺省目录(/usr/include && /usr/local/include)不用指定。

另外,-include 用来包含某个头文件,但一般情况下因为已经在源码里用 #include 指令实现了,所以这个很少用。


-I dir

在头文件的搜索路径列表中添加dir目录

-L dir

在库文件的搜索路径列表中添加dir目录


[root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq

需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。


CFLAGS = -g -O2  -Wall -Werror -Wno-unused
 
编译出现警告性错误unused-but-set-variable,变量定义但没有使用,解决方法:
 增加 CFLAGS 或CPPFLAGS参数如下:
  CPPFLAGS=" -Werror -Wno-unused-but-set-variable" || exit 1
 

                                          Gcc总体选项列表

后 缀 名

所对应的语言

-S

只是编译不汇编,生成汇编代码

-E

只进行预编译,不做其他处理

-g

在可执行程序中包含标准调试信息

-o file

把输出文件输出到file里

-v

打印出编译器内部编译各过程的命令行信息和编译器的版本

-I dir

在头文件的搜索路径列表中添加dir目录

-L dir

在库文件的搜索路径列表中添加dir目录

-static

链接静态库

-llibrary

连接名为library的库文件

   

 

· “-I dir”

正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,Gcc就会到相应的位置查找对应的目录

比如在“/root/workplace/Gcc”下有两个文件:

#include

int main()

{

     printf(“Hello!!\n”);

     return 0;

}

#include

这样,就可在Gcc命令行中加入“-I”选项:

[root@localhost Gcc] Gcc hello1.c –I /root/workplace/Gcc/ -o hello1

这样,Gcc就能够执行出正确结果。

小知识

在include语句中,“<>”表示在标准路径中搜索头文件,““””表示在本目录中搜索。故在上例中,可把hello1.c的“#include”改为“#include “my.h””,就不需要加上“-I”选项了。

· “-L dir”

选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录。例如有程序hello_sq.c需要用到目录“/root/workplace/Gcc/lib”下的一个动态库libsunq.so,则只需键入如下命令即可:

[root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq

需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。

另外值得详细解释一下的是“-l”选项,它指示Gcc去连接库文件libsunq.so。由于在Linux下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库文件名时可以省去lib三个字母。也就是说Gcc在对”-lsunq”进行处理时,会自动去链接名为libsunq.so的文件。


(3)优化选项

Gcc可以对代码进行优化,它通过编译选项“-On”来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的Gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

不同的优化级别对应不同的优化处理工作。

           如使用优化选项“-O”主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。

          使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。

          选项“-O3”则还包括循环展开和其他一些与处理器特性相关的优化工作。

虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。所以笔者建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。





 静态链接库其实就可以看成是 *.o 文件的简单打包:

ar -cr libtest.a test.o      或: ar -cr libtest.a test.cpp

-r 选项是必须的,表示插入到备份文件,并且有则替换 -c 表示创建备份文件


、编译目标代码时 ”-Wl,-rpath,” 指定的动态库搜索路径(当指定多个动态库搜索路径时,路径之间用冒号”:”分隔。);

2、环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径;


静态检查

-pedantic     //检查代码是否符合 ANSI/ISO 标准:
-Wall       //输出警告信息
-Wextra      //输出更多的警告信息
-Weffc++      //检查是否符合 《Effective C++》这本书中提到的标准
-w        //关闭所有警告


调试与优化

-g            //在编译时生成原生格式的调试符号信息,可以使用 gdb 或 ddx 等调试器调试。-g 分为三个级别,默认为 -g2,其中 -g3 除包含 -g2 中的所有调试信息外,还包含源代码中定义的宏。
-ggdb          //在编译时生成 gdb 专用格式的调试符号信息,信息更为丰富,但只能使用 gdb 调试,而不能使用其它调试器。级别设定与 -g 基本相同。
-gdwarf        //如果升级 GCC 之后,发现 GDB 不能用了,那么在编译的时候加上 -gdwarf 选项试一下,如果还不行,再使用 -gdwarf-2 试试。
-p           //将剖析(Profiling)信息加入到最终生成的二进制代码中,生成可以被通用剖析工具(Prof)能够识别的统计信息,它对于找出程序瓶颈很有帮助。生成后的可执行文件要运行一次,才能生成剖析文件,默认文件名是 gmon.out。
-pg            //生成只有 GNU 剖析工具(Gprof)才能识别的统计信息。
-save-temps     //保存编译过程中生成的一系列中间文件。
-On          //n 可以为 0~3,默认为 1,数字越大优化越高,一般使用 -O2 可以在优化长度、编译时间和代码大小之间取得较好平衡,开发调试建议使用 -O0
-Os          //相当于 -O2.5,使用了所有 -O2 的优化选项,但却没有使用增加大小。也就是说,相对于 -O2,能减少一点可执行文件的大小。
-s          //从执行文件中删除符号表与重定向信息,以减少执行文件的大小。跟直接编译之后,使用 strip 命令减小文件大小效果一致













  •  

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值