Linux gcc学习

hello.c到可执行文件a.out的过程,

第一步gcc -E hello.c -o hello.i,这一步是预处理器:cpp,它的作用就在于把头文件展开,把你定义的宏替换掉,注释去掉

第二步gcc -S hello.i  -o hello.s,这一步是编译器:gcc,把你的C语言文件变成汇编文件

第三步gcc -c hello.s -o hello.o,这一步是汇编器:as,把汇编文件变成二进制文件

第四步gcc hello.o -o hello,这一步是链接器:ld,将函数库相应的代码组合到目标文件中

假如说你的main.c文件中引用了头文件比如说是"head.h",main.c在src目录下,head.h在include目录下,include和src是同级目录,你直接去gcc main.c -o main就会报错,因为在src目录下找不到head.h,这样的解决办法就是你这样写gcc -Iinclude main.c -o main 

其实这里的-Iinclude其实应该是-I./include 表示寻找头文件的顺序./include-->/usr/include-->/usr/local/include 

还有一些常见的gcc错误:以下这些错误作为编程新手在最初使用IDE的时候是不是都有遇到过呢,ld的错误估计经常有吧,现在大家就可以弄清楚,为什么会有ld的错误了,其实链接是一个非常古老的技术,甚至比编译器的时间都要早,这部分的东西,我会在阅读完成深入了解计算机系统和程序员的自我修养——链接、装载与库的过程中写一些博客,欢迎大家与我交流

Gcc的错误类型及对策

 Gcc编译器如果发现源程序中有错误,就无法继续进行,也无法生成最终的可执行文件。为了便于修改,gcc给出错误资讯,我们必须对这些错误资讯逐个进行分析、处理,并修改相应的语言,才能保证源代码的正确编译连接。gcc给出的错误资讯一般可以分为四大类,下面我们分别讨论其产生的原因和对策。

第一类∶C语法错误

错误资讯∶文件source.c中第n行有语法错误(syntex errror)这种类型的错误,一般都是C语言的语法错误,应该仔细检查源代码文件中第n行及该行之前的程序,有时也需要对该文件所包含的头文件进行检查。有些情况下,一个很简单的语法错误,gcc会给出一大堆错误,我们最主要的是要保持清醒的头脑,不要被其吓倒,必要的时候再参考一下C语言的基本教材。

第二类∶头文件错误

错误资讯∶找不到头文件head.h(Can not find include file head.h

)。这类错误是源代码文件中的包含头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。

第三类∶档案库错误

错误资讯∶连接程序找不到所需的函数库,例如∶

ld: -lm: No such file or directory

这类错误是与目标文件相连接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等,检查的方法是使用find命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。

第四类∶未定义符号

错误资讯∶有未定义的符号(Undefined symbol)。这类错误是在连接过程中出现的,可能有两种原因∶一是使用者自己定义的函数或者全局变量所在源代码文件,没有被编译、连接,或者干脆还没有定义,这需要使用者根据实际情况修改源程序,给出全局变量或者函数的定义体;二是未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而连接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令ar检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改gcc连接选项中的-l和-L项。

排除编译、连接过程中的错误,应该说这只是程序设计中最简单、最基本的一个步骤,可以说只是开了个头。这个过程中的错误,只是我们在使用C语言描述一个算法中所产生的错误,是比较容易排除的。我们写一个程序,到编译、连接通过为止,应该说刚刚开始,程序在运行过程中所出现的问题,是算法设计有问题,说得更玄点是对问题的认识和理解不够,还需要更加深入地测试、调试和修改。一个程序,稍为复杂的程序,往往要经过多次的编译、连接和测试、修改。

gcc -D 这个主要是为了加上一些调试信息,比如说你的主程序里面需要#define DEBUG,比如你有一句想要在调试中打印的话,但是发布的时候你不想让他打印出来,这个时候你就可以用这个

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

int main()
{
#ifdef DEBUG
        printf("this is before/n");
#endif
        printf("helloworld/n");
#ifdef DEBUG
        printf("this is end/n");
#endif
        exit(0);
}

你就可以写gcc -DDEBUG -o test
如果你想这么写#define DEBUG 10;
那你就可以这么写gcc -DDEBUG=10 -o test;
与之对应的是-UMACRO参数,相当于#undef MACRO,取消宏定义

gcc -Wall是为了输出警告信息,gcc -g是为了可以调试

还有一个gcc -O1/2/3 三种不同等级的优化可以让你的冗余代码减少

举个简单例子:

int a = 10;
int b = a;
int c = b;
int b = c;
你其实就想让b = 10;但是你这样写起来非常的冗余,计算机去把这些东西理解也很费劲,执行起来既耗费时间,也让你的代码非常的多,所以这时候优化就是很必须的了

这里我贴一个别人详解这个优化选项的blog,大家慢慢看:https://blog.csdn.net/xinianbuxiu/article/details/51844994

-O1,-O2,-O3为何方神圣,它们是如何优化编译文件的?

(1)首先,她们的真面目是:

-O1 提供基础级别的优化

      -O2提供更加高级的代码优化,会占用更长的编译时间

      -O3提供最高级的代码优化

可以使用-f命令行选项引用每个单独的优化技术。

1, 编译器优化级别1

在优化的第一个级别执行基础代码的优化

这个级别试图执行9种单独的优化功能:

(1).-fdefer-pop: 这种优化技术与汇编语言代码在函数完成时如何进行操作有关。

(2).-fmerge-constans: 使用这种优化技术, 编译器试图合并相同的常量.

(3) . -fthread-jumps: 使用这种优化技术与编译器如何处理汇编代码中的条件和非条件分支有关。 在某些情况下, 一条跳转指令可能转移到另一条分支语句。 通过一连串跳转, 编译器确定多个跳转之间的最终目标并且把第一个跳转重新定向到最终目标。

 

(4).-floop-optimize:通过优化如何生成汇编语言中的循环, 编译器可以在很大程序上提高应用程序的性能。通常, 程序由很多大型且复杂的循环构成。 通过删除在循环内没有改变值的变量赋值操作, 可以减少循环内执行指令的数量, 在很大程度上提高性能。 此外优化那些确定何时离开循环的条件分支,以便减少分支的影响。

(5).-fif-conversion: if-then语句应该是应用程序中仅次于循环的最消耗时间的部分。简单的if-then语句可能在最终的汇编语言代码中产生众多的条件分支。 通过减少或者删除条件分支, 以及使用条件传送 设置标志和使用运算技巧来替换他们, 编译器可以减少if-then语句中花费的时间量。

(6)-fif-conversion2: 这种技术结合更加高级的数学特性, 减少实现if-then语句所需的条件分支。

(7)-fdelayed-branch: 这种技术试图根据指令周期时间重新安排指令。 它还试图把尽可能多的指令移动到条件分支前, 以便最充分的利用处理器的治理缓存。

(8) -fguess-branch-probability:就像其名称所暗示的, 这种技术试图确定条件分支最可能的结果, 并且相应的移动指令, 这和延迟分支技术类似。因为在编译时预测代码的安排,所以使用这一选项两次编译相同的c或者c++代码很可能会产生不同的汇编语言代码,这取决于编译时编译器认为会使用那些分支。

(9)-fcprop-registers: 因为在函数中把寄存器分配给变量, 所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并且删除不必要的寄存器复制操作。

2, 编译器优化级别2

结合了第一个级别的所有优化技术,再加上一下一些优化:

(1)-fforce-mem: 这种优化在任何指令使用变量前, 强制把存放再内存位置中的所有变量都复制到寄存器中。 对于只涉及单一指令的变量, 这样也许不会有很大的优化效果. 但是对于在很多指令(必须数学操作)中都涉及到的变量来说, 这会时很显著的优化, 因为和访问内存中的值相比 ,处理器访问寄存器中的值要快的多。

 

(2)-foptimize-sibling-calls: 这种技术处理相关的和/或者递归的函数调用。通常,递归的函数调用可以被展开为一系列一般的指令, 而不是使用分支。

(3)-fstrength-reduce: 这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器的变量, 比如使用变量, 然后使用循环计数器变量执行数学操作的for-next循环。

(4)-fgcse: 这些优化操作试图分析生成的汇编语言代码并且结合通用片段, 消除冗余的代码段。如果代码使用计算性的goto,gcc指令推荐

 

(5)-fcse-follow-jumps: 这种特别的通用子表达式消除技术扫描跳转指令, 查找程序中通过任何其他途径都不会到达的目标代码。这种情况最常见的例子就式if-then-else语句的else部分。

(6)-frerun-cse-after-loop: 这种技术在对任何循环已经进行过优化之后重新运行通用子表达式消除例程。这样确保在展开循环代码之后更进一步地优化还编代码。

(7)-fdelete-null-pointer-checks: 这种优化技术扫描生成的汇编语言代码, 查找检查空指针的代码。

(8)-fextensive-optimizations: 这种技术执行从编译时的角度来说代价高昂的各种优化技术,但是它可能对运行时的性能产生负面影响。

(9)-fregmove: 编译器试图重新分配mov指令中使用的寄存器, 并且将其作为其他指令操作数, 以便最大化捆绑的寄存器的数量。

(10)-fschedule-insns: 编译器将试图重新安排指令, 以便消除等待数据的处理器。对于在进行浮点运算时有延迟的处理器来说, 这使处理器在等待浮点结果时可以加载其他指令。

 

(11)-fsched-interblock: 这种技术使编译器能够跨越指令块调度指令。 这可以非常灵活地移动指令以便等待期间完成的工作最大化。

(12)-fcaller-saves: 这个选项指示编译器对函数调用保存和恢复寄存器, 使函数能够访问寄存器值, 而且不必保存和恢复他们。 如果调用多个函数, 这样能够节省时间, 因为只进行一次寄存器的保存和恢复操作, 而不是在每个函数调用中都进行。

(13)-fpeephole2: 这个选项允许进行任何计算机特定的观察孔优化。

(14)-freorder-blocks: 这种优化技术允许重新安排指令块以便改进分支操作和代码局部性。

(15)-fstrict-aliasing: 这种技术强制实行高级语言的严格变量规则。 对于c和c++程序来说, 它确保不在数据类型之间共享变量. 例如, 整数变量不和单精度浮点变量使用相同的内存位置。

(16)-funit-at-a-time:这种优化技术指示编译器在运行优化例程之前读取整个汇编语言代码。这使编译器可以重新安排不消耗大量时间的代码以便优化指令缓存。

(17)-falign-functions:这个选项用于使函数对准内存中特定边界的开始位置。大多数处理器按照页面读取内存,并且确保全部函数代码位于单一内存页面内, 就不需要叫化代码所需的页面。

(18)-fcrossjumping: 这是对跨越跳转的转换代码处理, 以便组合分散在程序各处的相同代码。 这样可以减少代码的长度,但是也许不会对程序性能有直接影响。

 3, 编译器优化级别3

它整合了第一和第二级别中的左右优化技巧, 还包括一下优化:

-finline-functions:这种优化技术不为函数创建单独的汇编语言代码,而是把函数代码包含在调度程序的

代码中。 对于多次被调用的函数来说, 为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利, 但是通过最充分的利用指令缓存代码, 而不是在每次函数调用时进行分支操作, 可以提高性能。

-fweb: 构建用于保存变量的伪寄存器网络。 伪寄存器包含数据, 就像他们是寄存器一样, 但是可以使用各种其他优化技术进行优化, 比如cse和loop优化技术。

-fgcse-after-reload:这中技术在完全重新加载生成的且优化后的汇编语言代码之后执行第二次gcse优化,帮助消除不同优化方式创建的任何冗余段。

ok,第一阶段的gcc的参数学习就到这里,写这么多还是为了自己以后忘记这部分辛辛苦苦学到的知识。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值