gcc使用指南

显示版本:gcc --version

编译:
gcc -Wall hello.c -o hello
gcc -Wall hello.c
-Wall命令:warning all,很重要,如果有该参数,那么一些潜在的警告将会有提示
./hello 运行当前目录的hello。说明:在windows下,运行hello时,首先会在当前目录下搜索,
如果没有会到系统PATH指定的位置查找。但Linux和Unix会直接在PATH下查询,因此必须使用./


linux下使用中文
export LANG=C

============多文件编译===========
hello.h
void hello(const char * string);
hello.c
#include <stdio.h>
#include "hello.h"
void hello(const char * string)
{
    printf("%s",string);
}
main.c
#include <hello.h>
int main(void)
{
    char * string = "hello world!\n";
    hello(string);
    return 0;
}

编译:
gcc -Wall hello.c main.c -o main
""的include会先在当前目录查找该文件,如果没有就去系统目录查找;
<>的include会直接去系统目录找。
系统头文件目录一般在/usr/include或在/usr/local/include目录下

==========verbose选项===========
'-v'选项能够用于显示关于编译的详细的信息,如列出搜索头文件、库文件
等。便于调错。

===========独立编译文件==========
·如果源文件只有一个,那么任何一个修改都必须重新编译整个源文件,这是非常耗时的
·如果程序被保存在不同的文件中,那么只要对修改的源文件进行编译即可。此时需要将这些源
    文件单独编译,最后将这些编译后的目标文件链接起来
·在第一步,先将源文件编译为目标文件,在GCC中是.o的,在win下是.obj的
·在第二步,使用链接器将目标文件合并在一起,生成一个可执行文件
·目标文件包括机器码,指定了其他文件中的内存或函数。这样源码可独立编译。
创建目标文件的方法:
使用'-c'参数。如:gcc -Wall -c main.c
此时就不用使用-o了,因为不需要在此时产生可执行文件。名称与源文件相同
编译时不用指定头文件
gcc -Wall -c main.c
gcc -Wall -c hello.c
-->
最后一步是使用GCC链接所有的目标文件和填充缺失地址与额外函数来生成可执行文件。
gcc main.o hello.o -o hello
gcc只是个外壳,内部使用的链接器是ld
注意链接顺序(某些老的编译器,定义函数的文件放后面)
which gcc 查找gcc的目录。
因为链接要比编译快的多,所以这种独立编译的方式在大型软件中更好。
除此之外,也可以使用MAKE来管理多源文件的问题。

=============链接库文件(静态库)=============
library是很多目标文件的集合体。库主要是为了提供一些系统函数。
库文件通常是存在于.a文件中(win中是.lib),指定是静态库。是通过ar产生的静态库
标准库一般在/usr/lib和/lib下,标准库是隐式链接的
像math的一些数学库,虽然是系统提供的,但不是系统库,需要显示链接,但由于GCC现在
的版本比较高,这一步它帮我们已经自动做好了。
·显示指定静态库
gcc -Wall main.c /usr/lib/libm.a -o calc(链接数学库)
简便记法:
gcc -Wall main.c -lm -o calc
即:'-lNAME' 等价于 链接libNAME,这默认的是标准库目录

=============编译过程中常见错误===============
FILE.H: No such file or directory
/usr/bin/ld: cannot find library
上面错误是没有找到头文件或链接器没有找到相关的库
缺省情况下,GCC会在系统的头文件目录和库文件目录去找头文件与库文件
/usr/local/include/、/usr/include/
/usr/local/lib/、/usr/lib

=================告诉编译器搜索路径===============
[方式一:使用-I或-L参数]
如果是64们机器可能还会在lib64下找
如果所需的库不在标准目录下,那么就要指定目录以让编译器找到。
使用'-I'和'-L'去找头文件和库文件(此两参数都是指定目录)
'-lNAME'指定是一个库文件
注意#include所的头文件不要使用绝对路径。否则源码转移位置后转不能正确编译。
使用-I添加头文件路径
[方式二:定义一些环境变量]
可以将所需的环境变量放在'_bash_profile'文件中,这样登录之后这些变量就会自动设置好。
使用C_INCLUDE_PATH查找C头文件目录、使用CPLUS_INCLUDE_PATH查找C++头文件目录
使用LIBRARY_PATH设置库文件目录
如果以上两种方式都使用了的话,那么
1、参数方式
2、环境变量
3、系统目录
注意:推荐使用第一种参数方式

================使用ar命令创建库文件================
很多目标文件组成为一个库文件
ar cr libNAME.a file1.o file2.o file2.o
查看库文件中有多少目标文件组成的
ar t libNAME
编译过程如下:
.
|-- header
|   |-- reply.h
|   `-- sayh.h
|-- lib
|   |-- libtalk.a
|   |-- reply.c
|   `-- sayh.c
|-- sayhe
`-- sayhe.c
在lib目录中生成目标文件:
gcc -Wall -c sayh.c -I ../header/
gcc -Wall -c reply.c -I ../header/
生成库文件:
ar cr libtalk.a sayh.o reply.o
最终编译:
gcc -Wall sayhe.c -I ./header/ -L ./lib/ -ltalk -o sayhe
如果出现错误:undefined reference to `sayHello'
可能是因为只指定了库目录但没有用-l指定库文件名

使用系统环境变量:将头文件与库文件目录指定一下,这样就不用在编译时指定了
export LIBRARY_PATH=./lib/:$LIBRARY_PATH
export C_INCLUDE_PATH=./header/:$C_INCLUDE_PATH
gcc -Wall sayhe.c -ltalk -o newsayhe

============静态库与动态库===============
扩展库一般分为静态库(static library)与动态库(shared library,共享库)
静态库是以'.a'的文件,当程序链接的时候,会将静态库中的所有机器码都拷贝到目标执行
文件中。
动态库是以'.so'的文件,当程序链接的时候,只会拷贝动态库中函数的地址,在程序运行时,
才去将动态库load到内存中。
============动态库===============
动态库在链接程序时只会拷贝它需要的一个函数表而不是将整个机器码进行拷贝。
在程序运行前,动态库中的机器码将被操作系统加载到内存中。
动态链接使可执行文件变得更小,节省了磁盘空间。
大多数操作也会提供虚拟内存机制,使内存中如果有多个程序调用一个动态库的时候,内存中
只有一份动态库机器码,可节省内存。
动态库使更新库时不需要重新编译源程序,在调用接口不变的情况下。
因为这些优点,GCC编译程序时优先使用动态库。
当可执行程序执行时,会将动态库加载到内存中。默认情况下,会搜索'/usr/local/lib'或
'/usr/lib'
此外,可以用环境变量'LD_LIBRARY_PATH'来指定动态库的目录。(如果是windows下使用PATH)
注意:LD_LIBRARY_PATH是在执行时搜索动态库用的,而LIBRARY_PATP是在链接时使用的

动态库编译过程:
.
|-- dyso
|-- dyso.c
|-- header
|   |-- add.h
|-- lib
|   |-- libadd.so
|   `-- soadd.c
-------add.h--------
int add(int a,int b);
-------soadd.c--------
#include <stdio.h>
#include "add.h"
int add(int a,int b)
{
    return a + b;
}
gcc -shared soadd.c -I ../header/ -o libadd.so
file ./lib/libadd.so
./lib/libadd.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
说明是动态链接库了
-------dyso.c--------
#include <stdio.h>
#include "add.h"
int main(void)
{
 int a,b,c;
 puts("input a and b ,i will get a+b");
 printf("a:");
 scanf("%d %d",&a,&b);
 c = add(a,b);
 printf("%d + %d = %d\n",a,b,c);
 return 0;
}

编译:
gcc -Wall dyso.c -I ./header/ -L ./lib/ -ladd -o dyso
运行:
./dyso: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
找不导连接库,配置环境变量:
 export LD_LIBRARY_PATH=./lib/:$LD_LIBRARY_PATH
./dyso
input a and b ,i will get a+b
a:2 3
2 + 3 = 5

============GCC支持的C语言标准===========
'-std=c89'            ANSIC
'-std=iso989'
'-std=c99'
'-ansi'
如果不加以上参数,将按照GCC的方言形式来编译。
完全按照ANSI标准来编译:gcc -Wall -ansi -pedantic v.c

==============='-Wall'选项=================
该参数允许检查一些常见的错误,通常分为以下:
'-Wcomment'
检查注释是否嵌套了。
注释代码也可以用:#if 0   #endif
'-Wformat'
使用printf与scanf时传入的格式是否有误
'-Wunused'
检查没有使用的变量
'-Wimplicit'
检查隐式的,没有声明的函数,最常见的是没有包含相应的头文件
'-Wreturn-type'
函数没有返回值,但没有声明为void,或者其他。

其他一些不常见警告
'-W'
'-Wconversion'
'-Wshadow'
'-Wcast-qual'
'-Wwrite-strings'
'-Wtraditional'

=============预处理===============
gcc预处理会进行宏的替换。如#include <stdio.h>会将该头文件的内容完全拷贝进来。
有:dtest.c
#include <stdio.h>
int main(void)
{
    #ifdef TEST
    printf("defination test!\n");
    #endif
    return 0;
}

gcc -Wall dtest.c -o dtest
这样没有任何输出
gcc -Wall -DTEST dtest.c -o dtest
预定义的宏
cpp -dM /dev/null(cpp代表c preprocessor)
有:dtest2.c
#include <stdio.h>
int main(void)
{
    printf("Value of NUM is %d\n",NUM);
    return 0;
}

gcc -Wall -DNUM=2 dtest2.c -o dtest2
如果不写=2缺省为1

'-E' 只希望GCC只处理预处理而不进行编译
有:test.c
#define MSG "hello beijing"
int main(void)
{
    const char str[] = MSG;
    return 0;
}

运行:gcc -Wall -E test.c
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "test.c"

int main(void)
{
 const char str[] = "hello beijing";
 return 0;
}

这样理解一些复杂的宏的效果是有好处的。
gcc -Wall -c save-temps hello.c
会产生中间过程的文件,如.i(预处理结果),.s(汇编结果)

=============GCC debug=============
编译时使用'-g'命令,将调试信息加入到可执行程序中。
GCC的调试器叫做 GNU Debugger 即 gdb
程序如果非正常退出的话,操作系统会为程序生成一个core文件
有:null.c
----------------
#include <stdio.h>
int a(int * p);
int main(void)
{
    int *p = NULL;//此处是空指针,地址0是内核地址,不允许程序访问
    a(p);
    return 0;
}
int a(int * p)
{
    int y = *p;
    return y;
}

编译:gcc -Wall -g null.c -o null
运行:./null
段错误
因为core文件一般很大,如果多次运行时产生core文件将很占存储空间,所以默认情况下,
操作系统不允许产生core文件,或者被操作系统限制了大小,可以用ulimit来查看:
[c@localhost tmp]$ ulimit -c
0
设置为不限制:unlimit -c unlimited
再次运行:
段错误 (core dumped)
生成一个core文件:
core.12862
使用gdb查看在哪里宕掉的
gdb null core.12862
输入where会跳转到宕机的位置
Program terminated with signal 11, Segmentation fault.
#0  0x0804838e in a (p=0x0) at null.c:12
12        int y = *p;
(gdb) where(或backtrace)
#0  0x0804838e in a (p=0x0) at null.c:12
#1  0x08048377 in main () at null.c:7
打印下 p
(gdb) p p
$1 = (int *) 0x0

==============编译优化===============
源码级优化
[公有子表达式消除(Common Subexpression Elimination,CSE):
    减少程序体积增加运行速度
内嵌函数Function inlining,FL
    减少函数调用间的时耗(尤其对于小函数且调用多次的函数)
循环减少判断,但会增加体积
]
-------------
机器指令层优化

优化:gcc -Wall -O0 test.c -o o0//不优化
优化:gcc -Wall -O1 test.c -o o1
优化:gcc -Wall -O2 test.c -o o2
优化:gcc -Wall -O3 test.c -o o3
看一个程序执行时间:
time ./a.out
real 0m0.002s
user 0m0.000s    //用户态
sys     0m0.002s    //系统态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值