gcc 编译 c++ 程序(转载)
单个源文件生成可执行程序
下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码:
/* helloworld.cpp */ #include <iostream> int main(int argc,char *argv[]) { std::cout << "hello, world" << std::endl; return(0); }
$ g++ helloworld.cpp
编译器 g++ 通过检查命令行中指定的文件的后缀名可识别其为 C++源代码文件。编译器默认的动作:编译源代码文件生成对象文件(object file),链接对象文件和 libstdc++ 库中的函数得到可执行程序。然后删除对象文件。由于命令行中未指定可执行程序的文件名,编译器采用默认的 a.out。程序可以这样来运行:
$ ./a.out
hello, world
更普遍的做法是通过 -o 选项指定可执行程序的文件名。下面的命令将产生名为 helloworld 的可执行文件:
$ g++ helloworld.cpp -o helloworld
在命令行中输入程序名可使之运行:
$ ./helloworld
hello, world
程序 g++ 是将 gcc 默认语言设为 C++ 的一个特殊的版本,链接时它自动使用 C++ 标准库而不用 C 标准库。通过遵循源码的命名规范并指定对应库的名字,用 gcc 来编译链接 C++ 程序是可行的,如下例所示:
$ gcc helloworld.cpp -lstdc++ -o helloworld
选项 -l (ell) 通过添加前缀 lib 和后缀 .a 将跟随它的名字变换为库的名字 libstdc++.a。而后它在标准库路径中查找该库。gcc 的编译过程和输出文件与 g++ 是完全相同的。
在大多数系统中,GCC 安装时会安装一名为 c++ 的程序。如果被安装,它和 g++ 是等同,如下例所示,用法也一致:
$ c++ helloworld.cpp -o helloworld
多个源文件生成可执行程序
如果多于一个的源码文件在 g++ 命令中指定,它们都将被编译并被链接成一个单一的可执行文件。下面是一个名为 speak.h 的头文件;它包含一个仅含有一个函数的类的定义:
/* speak.h */ #include <iostream> class Speak { public: void sayHello(const char *); };
/* speak.cpp */ #include "speak.h" void Speak::sayHello(const char *str) { std::cout << "Hello " << str << "\n"; }
/* hellospeak.cpp */ #include "speak.h" int main(int argc,char *argv[]) { Speak speak; speak.sayHello("world"); return(0); }
$ g++ hellospeak.cpp speak.cpp -o hellospeak
PS:这里说一下为什么在命令中没有提到“speak.h“该文件(原因是:在“speak.cpp“中包含有”#include"speak.h"“这句代码,它的意思是搜索系统头文件目录之前将先在当前目录中搜索文件“speak.h“。而”speak.h“正在该目录中,不用再在命令中指定了)。
源文件生成对象文件
选项 -c 用来告诉编译器编译源代码但不要执行链接,输出结果为对象文件。文件默认名与源码文件名相同,只是将其后缀变为 .o。例如,下面的命令将编译源码文件 hellospeak.cpp 并生成对象文件 hellospeak.o:
$ g++ -c hellospeak.cpp
命令 g++ 也能识别 .o 文件并将其作为输入文件传递给链接器。下列命令将编译源码文件为对象文件并将其链接成单一的可执行程序:
$ g++ -c hellospeak.cpp
$ g++ -c speak.cpp
$ g++ hellospeak.o speak.o -o hellospeak
选项 -o 不仅仅能用来命名可执行文件。它也用来命名编译器输出的其他文件。例如:除了中间的对象文件有不同的名字外,下列命令生将生成和上面完全相同的可执行文件:
$ g++ -c hellospeak.cpp -o hspk1.o
$ g++ -c speak.cpp -o hspk2.o
$ g++ hspk1.o hspk2.o -o hellospeak
编译预处理
选项 -E 使 g++ 将源代码用编译预处理器处理后不再执行其他动作。下面的命令预处理源码文件 helloworld.cpp 并将结果显示在标准输出中:
本文前面所列出的 helloworld.cpp 的源代码,仅仅有六行,而且该程序除了显示一行文字外什么都不做,但是,预处理后的版本将超过 1200 行。这主要是因为头文件 iostream 被包含进来,而且它又包含了其他的头文件,除此之外,还有若干个处理输入和输出的类的定义。
预处理过的文件的 GCC 后缀为 .ii,它可以通过 -o 选项来生成,例如:
$ gcc -E helloworld.cpp -o helloworld.ii
生成汇编代码
选项 -S 指示编译器将程序编译成汇编语言,输出汇编语言代码而后结束。下面的命令将由 C++ 源码文件生成汇编语言文件 helloworld.s:
$ g++ -S helloworld.cpp
生成的汇编语言依赖于编译器的目标平台。
创建静态库
静态库是编译器生成的一系列对象文件的集合。链接一个程序时用库中的对象文件还是目录中的对象文件都是一样的。库中的成员包括普通函数,类定义,类的对象实例等等。静态库的另一个名字叫归档文件(archive),管理这种归档文件的工具叫 ar 。
在下面的例子中,我们先创建两个对象模块,然后用其生成静态库。
头文件 say.h 包含函数 sayHello() 的原型和类 Say 的定义:
/* say.h */ #include <iostream> void sayhello(void); class Say { private: char *string; public: Say(char *str) { string = str; } void sayThis(const char *str) { std::cout << str << " from a static library\n"; } void sayString(void); };
/* say.cpp */ #include "say.h" void Say::sayString() { std::cout << string << "\n"; } Say librarysay("Library instance of Say");
源码文件 sayhello.cpp 是我们要加入到静态库中的第二个对象文件的源码。它包含函数 sayhello() 的定义:
/* sayhello.cpp */ #include "say.h" void sayhello() { std::cout << "hello from a static library\n"; }
$ g++ -c sayhello.cpp
$ g++ -c say.cpp
$ ar -r libsay.a sayhello.o say.o
程序 ar 配合参数 -r 创建一个新库 libsay.a 并将命令行中列出的对象文件插入。采用这种方法,如果库不存在的话,参数 -r 将创建一个新的库,而如果库存在的话,将用新的模块替换原来的模块。
下面是主程序 saymain.cpp,它调用库 libsay.a 中的代码:
/* saymain.cpp */ #include "say.h" int main(int argc,char *argv[]) { extern Say librarysay; Say localsay = Say("Local instance of Say"); sayhello(); librarysay.sayThis("howdy"); librarysay.sayString(); localsay.sayString(); return(0); }
$ g++ saymain.cpp libsay.a -o saymain
程序运行时,产生以下输出:
hello from a static library
howdy from a static library
Library instance of SayLocal instance of Say
动态链接库的编写和使用
这里我们用到4个文件,它们分别为:SoDemoTest.h、one.cpp、two.cpp、three.cpp。它们的内容如下:
SoDemoTest.hifndef __SO_DEMO_TEST_HEADER__ #define __SO_DEMO_TEST_HEADER__ #include <iostream> using namespace std; void one(); void two(); void three(); #endif
one.cpp
#include "SoDemoTest.h" void one() { cout << "call one() function." << endl; }
two.cpp
#include "SoDemoTest.h" void two() { cout << "call two() function." << endl; }
three.cpp
#include "SoDemoTest.h" void three() { cout << "call three() function." << endl; }
2、动态库的连接
main.cpp
#include "SoDemoTest.h" int main() { one(); two(); three(); return 0; }
将main.cpp与libtest.so链接成一个可执行文件main。命令如下:
$ g++ main.cpp -L. -ltest -o main
测试可执行程序main是否已经链接的动态库libtest.so,如果列出了libtest.so,那么就说明正常链接了。可以执行以下命令:
$ ldd main
执行main可以看看main是否调用了动态链接库中的函数。
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。
相当于一个可执行文件
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,
而不能达到真正代码段共享的目的。
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory 出现这个错误时候,就要设置
4、注意的问题
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
动态调用SO文件
1.制作so文件:libadd_c.so
add.c:
int add(int a, int b) { return a+b; }
编译:
gcc -shared -fpic -lm -ldl -o libadd_c.so add.c
2.编写测试函数
test.cpp
#include <stdio.h> #include <dlfcn.h> #include <stdlib.h> #include <iostream> using namespace std; int main() { int a = 0; void *handle = dlopen("./libadd_c.so", RTLD_LAZY); if(!handle) { printf("open lib error\n"); cout<<dlerror()<<endl; return -1; } typedef int (*add_t)(int a, int b); add_t add = (add_t) dlsym(handle, "add"); if(!add) { cout<<dlerror()<<endl; dlclose(handle); return -1; } a = add(3, 4); printf("a = %d\n",a); dlclose(handle); return 0; }
编译:
g++ test.cpp -ldl -o test
3.运行
./test
介绍一下上面用到的接口函数
1) dlopen
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
a.根据环境变量LD_LIBRARY_PATH查找
b.根据/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。
2) dlerror
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
3) dlsym
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,
4) dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。
好了,现在来编译打包,命令如下:
$ g++ -shared -fPIC -o libhello.so hello.cpp
$ g++ main.cpp -ldl
在上面dlopen函数中,看到我们传的第一个参数并没有指定路径,只给出了库的名称。那是因为已经在环境变量LD_LIBRARY_PATH中指定了 ./ 目录,如下图所示。
如果你想放在其他目录,修改该环境变量即可。
gcc/g++ 动态编译和链接问题
-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名了。
好了现在我们知道怎么得到库名,当我们自已要用到一个第三方提供的库名字libtest.so,那么我们只要把libtest.so拷贝到/usr/lib里,编译时加上-ltest参数,我们就能用上libtest.so库了(当然要用libtest.so库里的函数,我们还需要与 libtest.so配套的头文件)。
放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了,但如果库文件没放在这三个目录里,而是放在其他目录里,这时我们只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find -lxxx”,也就是链接程序ld在那3个目录里找不到libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库,它在/usr /X11R6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -lX11参数,-L参数跟着的是库文件所在的目录名。再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L /aaa/bbb/ccc -ltest。
另外,大部分libxxxx.so只是一个链接,以RH9为例,比如libm.so它链接到/lib/libm.so.x,/lib/libm.so.6又链接到/lib/libm-2.3.2.so,如果没有这样的链接,还是会出错,因为ld只会找libxxxx.so,所以如果你要用到xxxx库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一个链接就可以了ln -s libxxxx-x.x.x.so libxxxx.so。
手工来写链接参数总是很麻烦的,还好很多库开发包提供了生成链接参数的程序,名字一般叫xxxx-config,一般放在/usr/bin目录下,比如gtk1.2的链接参数生成程序是gtk-config,执行gtk-config --libs就能得到以下输出"-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic-lgmodule -lglib -ldl -lXi -lXext -lX11 -lm",这就是编译一个gtk1.2程序所需的gtk链接参数,xxx-config除了--libs参数外还有一个参数是--cflags用来生成头文件包含目录的,也就是-I参数,在下面我们将会讲到。你可以试试执行gtk-config --libs --cflags,看看输出结果。
现在的问题就是怎样用这些输出结果了,最笨的方法就是复制粘贴或者照抄,聪明的办法是在编译命令行里加入这个`xxxx-config --libs --cflags`,比如编译一个gtk程序:gcc gtktest.c `gtk-config --libs --cflags`这样就差不多了。注意`不是单引号,而是1键左边那个键。
-include和-I参数:
-include用来包含头文件,但一般情况下包含头文件都在源码里用#include xxxxxx实现,-include参数很少用。-I参数是用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,如果不加你会得到一个"xxxx.h: No such file or directory"的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。
gcc & g++现在是gnu中最主要和最流行的c & c++编译器 。
g++是c++的命令,以.cpp为主,对于c语言后缀名一般为.c。这时候命令换做gcc即可。其实是无关紧要的。
其实编译器是根据gcc还是g++来确定是按照C标准还是C++标准编译链接。
下面以Test.cpp为例:
命令:
g++
Test.cpp
功能:生成默认为a.exe的文件,这个过程包含了编译和链接。
再说下-o命令,-o命令表示输出的意思,gcc/g++命令是非常灵活的,你不指定输出的文件名的时候默认生成的是.exe文件。
你要输出Test.exe的话可以用:g++
-o Test.exe Test.cpp。-o命令是输出的意思,这样就输出了Test.exe。
gcc/g++在执行编译工作的时候,总共需要以下几步:
/************************* Test.cpp *************************/ #include <IOSTREAM> static int t = 1; #define T 9 using namespace std; typedef int Status; int main() { Status i = 1; cout << T * i << endl; //Test Cout return 0; }
1.预处理,生成.i的文件[预处理器cpp]
命令:g++ -E Test.cpp > Test.i
功能:输出预处理后的文件,linux下以.i为后缀名。只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里 。
这一步主要做了这些事情:宏的替换,还有注释的消除,还有找到相关的库文件。用编辑器打开Test.i会发现有很多很多代码,
你只需要看最后部分就会发现,预处理做了宏的替换,还有注释的消除,可以理解为无关代码的清除。下面是Test.i文件的最后部分,
可以看见宏的替换和注释的消除。
# 5 "Test.cpp" 2 static int t = 1; using namespace std; typedef int Status; int main() { Status i = 1; cout << 9 * i << endl; return 0; }
2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs]
命令:g++ -S Test.cpp
功能:会生成Test.s文件,.s文件表示是汇编文件,用编辑器打开就都是汇编指令。
3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
命令:g++ -c Test.cpp
功能:.o是GCC生成的目标文件,除非你是做编译器和连接器调试开发的,否则打开这种.o没有任何意义。二进制机器码一般人也读不了。
4.连接目标代码,生成可执行程序[链接器ld]
命令:g++ Test.o
-L F:\vs2008\VC\include\iostream
功能:将.o文件与所需的库文件链接整合形成.exe文件,这就是可执行文件。-L 表示链接,这里我后面写的是绝对路径,相对各人电脑不同
在上面各个步骤中你可以用-o命令输出你自己想要的各种名字。比如最后一个命令,用下面的输出Test.exe
你可以g++ Test.o -o Test.exe -L F:\vs2008\VC\include\iostream
====================================================================================================
gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步
1.预处理,生成.i的文件[预处理器cpp]
2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs]
3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
4.连接目标代码,生成可执行程序[链接器ld]
[参数详解]
-x language filename
设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性,
决定你的C代码文件的后缀名是.pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。
可以使用的参数吗有下面的这些
`c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `assembler-with-cpp'.
看到英文,应该可以理解的。
例子用法:
gcc -x c hello.pig
-x none filename
关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型
例子用法:
gcc -x c hello.pig -x none hello2.c
-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c
他将生成.o的obj文件
-S
只激活预处理和编译,就是指把文件编译成为汇编代码。
例子用法
gcc -S hello.c
他将生成.s的汇编代码,你可以用文本编辑器察看
-E
只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面.
例子用法:
gcc -E hello.c > pianoapan.txt
gcc -E hello.c | more
慢慢看吧,一个hello word 也要与处理成800行的代码
-o
制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感,改掉它,哈哈
例子用法
gcc -o hello.exe hello.c (哦,windows用习惯了)
gcc -o hello.asm -S hello.c
-pipe
使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题
gcc -pipe -o hello.exe hello.c
-ansi
关闭gnu c中与ansi c不兼容的特性,激活ansi c的专有特性(包括禁止一些asm inline typeof关键字,以及UNIX,vax等预处理宏,
-fno-asm
此选项实现ansi选项的功能的一部分,它禁止将asm,inline和typeof用作关键字。
-fno-strict-prototype
只对g++起作用,使用这个选项,g++将对不带参数的函数,都认为是没有显式的对参数的个数和类型说明,而不是没有参数.
而gcc无论是否使用这个参数,都将对没有带参数的函数,认为城没有显式说明的类型
-fthis-is-varialble
就是向传统c++看齐,可以使用this当一般变量使用.
-fcond-mismatch
允许条件表达式的第二和第三参数类型不匹配,表达式的值将为void类型
-funsigned-char
-fno-signed-char
-fsigned-char
-fno-unsigned-char
这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前两个参数)或者 signed char(后两个参数)
-include file
包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设定,功能就相当于在代码中使用#include<filename>
例子用法:
gcc hello.c -include /root/pianopan.h
-imacros file
将file文件的宏,扩展到gcc/g++的输入文件,宏定义本身并不出现在输入文件中
-Dmacro
相当于C语言中的#define macro
-Dmacro=defn
相当于C语言中的#define macro=defn
-Umacro
相当于C语言中的#undef macro
-undef
取消对任何非标准宏的定义
-Idir
在你是用#include"file"的时候,gcc/g++会先在当前目录查找你所制定的头文件,如果没有找到,他回到缺省的头文件目录找,如果使用-I制定了目录,他
回先在你所制定的目录查找,然后再按常规的顺序去找.
对于#include<file>,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺省的头文件目录查找
-I-
就是取消前一个参数的功能,所以一般在-Idir之后使用
-idirafter dir
在-I的目录里面查找失败,讲到这个目录里面查找.
-iprefix prefix
-iwithprefix dir
一般一起使用,当-I的目录查找失败,会到prefix+dir下查找
-nostdinc
使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置
-nostdin C++
规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创libg++库使用
-C
在预处理的时候,不删除注释信息,一般和-E使用,有时候分析程序,用这个很方便的
-M
生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用gcc -M hello.c来测试一下,很简单。
-MM
和上面的那个一样,但是它将忽略由#include<file>造成的依赖关系。
-MD
和-M相同,但是输出将导入到.d的文件里面
-MMD
和-MM相同,但是输出将导入到.d的文件里面
-Wa,option
此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选项,然后传递给会汇编程序
-Wl.option
此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然后传递给会连接程序.
-llibrary
制定编译的时候使用的库
例子用法
gcc -lcurses hello.c
使用ncurses库编译程序
-Ldir
制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然
编译器将只在标准库的目录找。这个dir就是目录的名称。
-O0
-O1
-O2
-O3
编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-g
只是编译器,在编译的时候,产生调试信息。
-gstabs
此选项以stabs格式声称调试信息,但是不包括gdb调试信息.
-gstabs+
此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息.
-ggdb
此选项将尽可能的生成gdb的可以使用的调试信息.
-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么
动态连接库,就可以运行.
-share
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-traditional
试图让编译器支持传统的C语言特性
[参考资料]
-Linux/UNIX高级编程
中科红旗软件技术有限公司编著.清华大学出版社出版
-Gcc man page
[ChangeLog]
-2002-08-10
ver 0.1 发布最初的文档
-2002-08-11
ver 0.11 修改文档格式
-2002-08-12
ver 0.12 加入了对静态库,动态库的参数
-2002-08-16
ver 0.16 增加了gcc编译的4个阶段的命令
运行 gcc/egcs
**********运行 gcc/egcs***********************
GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。
利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。
如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。
例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。
代码:
-----------------------
清单 factorial.c
-----------------------
int factorial (int n)
{
if (n <= 1)
return 1;
else
return factorial (n - 1) * n;
}
-----------------------
清单 main.c
-----------------------
#include <stdio.h>
#include <unistd.h>
int factorial (int n);
int main (int argc, char **argv)
{
int n;
if (argc < 2)
{
printf ("Usage: %s n\n", argv [0]);
return -1;
}
else
{
n = atoi (argv[1]);
printf ("Factorial of %d is %d.\n", n, factorial (n));
}
return 0;
}
-----------------------
利用如下的命令可编译生成可执行文件,并执行程序:
$ gcc -o factorial main.c factorial.c
$ ./factorial 5
Factorial of 5 is 120.
GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C):
#include <iostream>
void main (void)
{
cout << "Hello, world!" << endl;
}
则可以如下调用 g++ 命令编译、连接并生成可执行文件:
$ g++ -o hello hello.C
$ ./hello
Hello, world!
**********************gcc/egcs 的主要选项*********
gcc 命令的常用选项
选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,
例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串“1”定义 MACRO 宏。
-DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
===========================================================================================
一:gcc与g++比较
编译c/c++代码的时候,有人用gcc,有人用g++,于是各种说法都来了,譬如c代码用gcc,而 c++代码用g++,或者说编译用gcc,链接用g++,一时也不知哪个说法正确,如果再遇上个extern "C",分歧就更多了,这里我想作个了结,毕竟知识的目的是令人更清醒,而不是更糊涂。
误区一:gcc只能编译c代码,g++只能编译c++代码
两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的,例如:
#include
int main(int argc, char* argv[]) {
if(argv == 0) return;
printString(argv);
return;
}
int printString(char* string) {
sprintf(string, "This is a test.\n");
}
如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:“printString未定义”;
“cannot convert `char**' to `char*”;
”return-statement with no value“;
分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。
误区二:gcc不会定义__cplusplus宏,而g++会
实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。
误区三:编译只能用gcc,链接只能用g++
严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。
误区四:extern "C"与gcc/g++有关系
实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。试验如下:
me.h:
extern "C" void CppPrintf(void);
me.cpp:
#include #include "me.h" using namespace std; void CppPrintf(void) { cout << "Hello\n"; } test.cpp: #include #include #include "me.h" int main(void) { CppPrintf(); return 0; }
1. 先给me.h加上extern "C",看用gcc和g++命名有什么不同
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv //注意此函数的命名
.type CppPrintf, @function
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv //注意此函数的命名
.type CppPrintf, @function
完全相同!
2. 去掉me.h中extern "C",看用gcc和g++命名有什么不同
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv //注意此函数的命名
.type _Z9CppPrintfv, @function
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv //注意此函数的命名
.type _Z9CppPrintfv, @function
完全相同!
【结论】完全相同,可见extern "C"与采用gcc/g++并无关系,以上的试验还间接的印证了前面的说法:在编译阶段,g++是调用gcc的。
二:gcc和g++的包含头文件库文件方法
-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名了。
好了现在我们知道怎么得到库名,当我们自已要用到一个第三方提供的库名字libtest.so,那么我们只要把 libtest.so拷贝到/usr/lib里,编译时加上-ltest参数,我们就能用上libtest.so库了(当然要用libtest.so库里 的函数,我们还需要与libtest.so配套的头文件)
放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了,但如果库文件没放 在这三个目录里,而是放在其他目录里,这时我们只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find -lxxx”,也就是链接程序ld在那3个目录里找不到libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库,它在/usr /X11R6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -lX11参数,-L参数跟着的是库文件所在的目录名。再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L /aaa/bbb/ccc -ltest
另外,大部分libxxxx.so只是一个链接,以RH9为例,比如libm.so它链接到/lib/libm.so.x,/lib/libm.so.6又链接到/lib/libm-2.3.2.so,
如果没有这样的链接,还是会出错,因为ld只会找libxxxx.so,所以如果你要用到xxxx库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一个链接就可以了ln -s libxxxx-x.x.x.so libxxxx.so
手工来写链接参数总是很麻烦的,还好很多库开发包提供了生成链接参数的程序,名字一般叫xxxx-config,一般放在/usr/bin目录下,比如
gtk1.2的链接参数生成程序是gtk-config,执行gtk-config --libs就能得到以下输出"-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic
-lgmodule -lglib -ldl -lXi -lXext -lX11 -lm",这就是编译一个gtk1.2程序所需的gtk链接参数,xxx-config除了--libs参数外还有一个参数是--cflags用来生成头 文件包含目录的,也就是-I参数,在下面我们将会讲到。你可以试试执行gtk-config --libs --cflags,看看输出结果
现在的问题就是怎样用这些输出结果了,最笨的方法就是复制粘贴或者照抄,聪明的办法是在编译命令行里加入这个 `xxxx-config --libs --cflags`,比如编译一个gtk程序:gcc gtktest.c `gtk-config --libs --cflags`这样就差不多了。注意`不是单引号,而是1键左边那个键。
5、-include和-I参数
-include用来包含头文件,但一般情况下包含头文件都在源码里用#include
xxxxxx实现,-include参数很少用。-I参数是用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找,但
是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I
/myinclude参数了,如果不加你会得到一个"xxxx.h:
No such file or directory"的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。
结论例子:
g++ curltest.cpp -o curltest -L/mnt/hgfs/windows/curl-7.19.5/lib/.libs -lcurl -I/mnt/hgfs/windows/curl-7.19.5/include
原文地址:https://blog.csdn.net/liujiayu2/article/details/49864381
posted on 2019-01-05 22:11 wangzhiliang 阅读(2418) 评论(0) 编辑 收藏 举报