编译C程序
多源文件到可执行文件
gcc编译程序可以自动处理连接,甚至在编译多个源文件的时候也可以自动连接。
sayhello.c
#include <stdio.h>
void sayhello(void) {
printf("hello, world\n");
}
hellomain.c
void sayhello(void);
int main(int argc, char *argv[]) {
sayhello();
return 0;
}
$ gcc hellomain.c sayhello.c -o hello
另外
$ objdump -f hello // objdump工具用于分析.o/.a/.so/ELF文件的信息,-f选项显示完整文件头内容
只进行预处理
-E选项指出,源代码只传递给预处理程序,结果默认显示在标准输出stdout上。
$ gcc -E sayhello.c -o hello.i
生成汇编代码
-S选项指示将程序编译成汇编语言,输出汇编源码,结果默认保存到.s
文件。
$ gcc -S sayhello.c
创建静态库
静态库是一些.o
文件的集合,管理工具为ar(archive,文档)。
hellofirst.c
#include <stdio.h>
void hellofirst(void) {
printf("The first hello\n");
}
hellosecond.c
#include <stdio.h>
void hellosecond(void) {
printf("The second hello\n");
}
$ gcc -c hellofirst.c hellosecond.c // 编译成.o文件
$ ar -r libhello.a hellofirst.o hellosecond.o // -r选项将.o目标文件插入到静态库(文档)。(如果文档中已存在此.o,就替换旧的)
另外
$ ar -xo libhello.a // -x选项将静态库展开成.o目标文件; -o选项指出展开时保留.o原始日期
$ ar -t libhello.a // -t选项显示静态库包含的所有.o目标文件
$ ar -v libhello.a // -v选项指出按照详细模式运行
$ nm hellofirst.o // nm工具可显示.o目标文件中的符号
$ nm libhello.a // nm工具可显示.a静态库中的符号
hellofirst.o:
00000000 T hellofirst
U puts
hellosecond.o:
00000000 T hellosecond
U puts
$ strip -Nhellofirst libhello.a // strip工具可删除.a静态库中的符号,-N选项指定删除的符号名
$ nm libhello.a // 使用strip删除.a中符号信息后再次使用nm
hellofirst.o:
U puts
hellosecond.o:
00000000 T hellosecond
U puts
$ objdump -S libhello.a // objdump工具用于分析.o/.a/.so/ELF文件的信息,-S选项展开libhello.a的可执行汇编代码
In archive libhello.a:
hellofirst.o: file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: c7 04 24 00 00 00 00 movl $0x0,(%esp)
d: e8 fc ff ff ff call 0xe
12: c9 leave
13: c3 ret
......
使用静态库
在连接时定位库规则:
1.大多数系统库保存在目录/lib、/usr/lib和/usr/local/lib中,因此会自动查找这些默认目录;
2.通过使用一个或多个-L选项,可以指定查找其他目录;
$ gcc -L. -L/home/lib prog.o
3.先查找共享库再查找静态库;
$ gcc -lmilt prog.o // 先为libmilt.so查找每个系统库目录,然后是libmilt.a
4.通过指定确切的库名可以限制所有的查找操作。
$ gcc libjj.a /home/libmilt.so prog.o // 连接时不在系统库目录查找libjj.a和libmilt.so
twohellos.c
void hellofirst(void);
void hellosecond(void);
int main(int argc, char *argv[]) {
hellofirst();
hellosecond();
return 0;
}
$ gcc twohellos.c ./libhello.a -o twohellos // 使用完整静态库名(lib*.a)限制连接时所有默认的查找操作
或者
$ gcc twohellos.c -L. -lhello -o twohellos // 在-L指定的目录下查找-l指定的缩写静态库名(先找libhello.so,找不到后再找libhello.a)
另外
$ nm twohellos // nm工具可显示ELF可执行文件中的符号
0804a020 B __bss_start
0804a020 b completed.6591
0804a018 D __data_start
0804a018 W data_start
...
08048434 T hellofirst
08048448 T hellosecond
080482b0 T _init
...
0804841d T main
U puts@@GLIBC_2.0
08048390 t register_tm_clones
08048320 T _start
0804a020 D __TMC_END__
08048350 T __x86.get_pc_thunk.bx
$ nm -D twohellos // nm工具可显示ELF可执行文件中的符号,-D选项只显示动态符号
w __gmon_start__
080484ec R _IO_stdin_used
U __libc_start_main
U puts
创建共享库
共享库也是一些.o
文件的集合,其中每个地址(变量引用和函数调用)都是相对地址,允许在运行程序的时候,可以动态加载和执行共享库。
shellofirst.c
#include <stdio.h>
void shellofirst(void) {
printf("The first hello from a shared library\n");
}
shellosecond.c
#include <stdio.h>
void shellosecond(void) {
printf("The second hello from a shared library\n");
}
$ gcc -c -fpic shellofirst.c shellosecond.c // -c指出编译成.o文件;-fpic指出按照可重定位地址方式生成
$ gcc -shared shellofirst.o shellosecond.o -o hello.so // -shared指出生成共享库
或者
$ gcc -fpic -shared shellofirst.c shellosecond.c -o hello.so
另外
$ nm -D hello.so // nm工具可显示.so共享库中的符号,-D选项只显示动态符号
0000201c B __bss_start
w __cxa_finalize
0000201c D _edata
00002020 B _end
000005a8 T _fini
w __gmon_start__
000003c8 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U puts
0000055b T shellofirst
00000581 T shellosecond
$ strip hello.so // strip工具可删除.so共享库中的符号
$ nm hello.so // 使用strip删除.so中符号信息后再次使用nm
nm: hello.so: no symbols
$ ldd hello.so // ldd工具可列出共享库hello.so的所有依赖关系
linux-gate.so.1 => (0xb77dd000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7612000)
/lib/ld-linux.so.2 (0xb77de000)
使用共享库
要使用共享库,必须在运行时能够找到共享库的位置。因为是通过名字而不是目录定位共享库,因此可以在连接时使用一个共享库,而在运行时使用另一个共享库。(因此大多数共享库会把版本号作为名字的一部分。)
运行时载入共享库按如下顺序:
1.UNIX程序采用ELF二进制文件格式,此格式动态段DT_RPATH中指出运行时动态库搜索目录,编译时使用GCC的“-Wl,-rpath”链接参数指定上述内容;
2.环境变量LD_LIBRARY_PATH指定的动态库搜索目录;
3.非文本文件/etc/ld.so.cache用于缓存共享库软连接列表,/etc/ld.so.conf文本文件中指定其他共享库搜索目录,由ldconfig工具维护;
4.系统库目录/lib和/usr/lib。
ldconfig工具按如下顺序更新共享库列表:
1.在/etc/ld.so.conf文件包含的目录(还有/lib和/usr/lib目录)中查找共享库;
2.然后分析共享库的名字和内容,如果内容为软连接,将在/etc/ld.so.cache文件中重新缓存软连接的指向内容。
3.将查找到的所有共享库列表更新到/etc/ld.so.cache文件中。
$ sudo ldconfig // 更新/etc/ld.so.cache文件(需要超级用户权限)
$ sudo ldconfig -v // 更新/etc/ld.so.cache文件,-v选项显示详细信息
$ ldconfig -p // -p选项显示/etc/ld.so.cache文件中共享库软连接列表
$ ldconfig -p | grep stdc++ // 在/etc/ld.so.cache文件中查找libstdc++.so
libstdc++.so.6 (libc6) => /usr/lib/i386-linux-gnu/libstdc++.so.6
stwohellos.c
void shellofirst(void);
void shellosecond(void);
int main(int argc, char *argv[]) {
shellofirst();
shellosecond();
return 0;
}
$ gcc stwohellos.c hello.so -o stwohellos
// 程序编译并连接到hello.so,但运行时需在系统库目录中定位到hello.so
或者
$ gcc stwohellos.c hello.so -Wl,-rpath=./ -o stwohellos
// 程序编译并连接到hello.so,但运行时需在系统库目录或-Wl,-rpath选项指定的目录中定位到hello.so
或者
$ gcc stwohellos.c ./hello.so -o stwohellos
// 程序编译并连接到hello.so,但运行时需在系统库目录或上述(./)目录中定位到hello.so
另外
$ nm -D stwohellos // nm工具可显示ELF可执行文件中的符号,-D选项只显示动态符号
0804a024 B __bss_start
0804a024 D _edata
0804a028 B _end
08048614 T _fini
w __gmon_start__
08048404 T _init
0804862c R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U __libc_start_main
U shellofirst
$ ldd stwohellos // ldd工具可列出ELF可执行文件的所有依赖关系
linux-gate.so.1 => (0xb7759000)
./hello.so (0xb7753000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb758e000)
/lib/ld-linux.so.2 (0xb775a000)
$ readelf -d stwohellos // readelf工具可查看ELF文件信息,若编译时使用GCC-Wl,-rpath选项,可看到RPATH段信息。
Dynamic section at offset 0xf04 contains 26 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [hello.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000f (RPATH) Library rpath: [./]
0x0000000c (INIT) 0x8048404
...
0x6ffffffe (VERNEED) 0x80483bc
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x80483a0
0x00000000 (NULL) 0x0
从共享库中载入函数
可以不把共享库连接到程序里,而只是加载并执行共享库里的某些函数。dlopen()
的第二个参数为RTLD_NOW
时,它让共享库中的所有函数都载入内存;参数为RTLD_LAZY
时,会推后每个函数的载入操作,直到它在调用dlsym()
中被应用。另外,使用此函数GCC编译时需连接系统库-ldl
。
sayhello.c
#include <stdio.h>
void sayhello(void) {
printf("Hello from a loaded function\n");
}
saysomething.c
#include <stdio.h>
void saysomething(char *string) {
printf("%s\n", string);
}
say.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#define err_exit(str) do { \
printf("%s\n", str); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char *argv[]) {
void *handle;
void (*sayhello)(void);
void (*saysomething)(char *);
handle = dlopen("./libsayfn.so", RTLD_LAZY);
if (NULL != dlerror())
err_exit(dlerror());
sayhello = dlsym(handle, "sayhello");
saysomething = dlsym(handle, "saysomething");
sayhello();
saysomething("This is something");
dlclose(handle);
return 0;
}
$ gcc -fpic -shared sayhello.c saysomething.c -o libsayfn.so // 创建共享库libsayfn.so
$ gcc say.c -ldl -o say // 使用dlopen类似的函数需连接库-ldl
自动生成函数原型
gcc为编译C程序提供-aux-info
选项,可以为.c
文件中的函数自动生成原型.h
。
$ gcc stwohellos.c -aux-info stwohellos.h // -aux-info选项后的参数为存放函数原型的文件名
编译C++程序
多源文件到可执行文件
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 // g++默认编译C++,自动连接到标准C++库(libstdc++.a)
或者
$ gcc hellospeak.cpp speak.cpp -lstdc++ -o hellospeak // gcc默认编译C,可通过-l选项使用标准C++库(libstdc++.a)编译
只进行预处理
-E选项指出,源代码只传递给预处理程序,结果默认显示在标准输出stdout上。
$ g++ -E speak.cpp -o speak.ii
生成汇编代码
-S选项指示将程序编译成汇编语言,输出汇编源码,结果默认保存到.s
文件。
$ g++ -S speak.cpp
创建静态库
say.h
#include <iostream>
void sayhello(void);
class Say {
private:
const char *string;
public:
Say(const 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(void) {
std::cout << string << "\n";
}
Say librarysay("Library instance of Say");
sayhello.cpp
#include "say.h"
void sayhello(void) {
std::cout << "hello from a static library\n";
}
$ g++ -c say.cpp sayhello.cpp
$ ar -r libsay.a sayhello.o say.o
使用静态库
saymain.cpp
#include "say.h"
int main(int argc, char *argv[]) {
extern Say librarysay; // libsay.a库中对象的引用
Say localsay = Say("Local instance of Say");
sayhello();
librarysay.sayThis("howdy");
librarysay.sayString();
localsay.sayString();
return 0;
}
$ g++ saymain.cpp libsay.a -o saymain
等同于
$ g++ saymain.cpp sayhello.cpp say.cpp -o saymain
:~$ ./saymain
hello from a static library
howdy from a static library
Library instance of Say
Local instance of Say
创建共享库
GNU支持C++语言的一些扩展功能。例如所有系统头文件默认包含,就好像它们被封装在extern "C" {...}
块中一样。
average.h
class Average {
private:
int count;
double total;
public:
Average(void) {
count = 0;
total = 0.0;
}
void insertValue(double value);
int getCount(void);
double getTotal(void);
double getAverage(void);
};
average.cpp
#include "average.h"
void Average::insertValue(double value) {
count++;
total += value;
}
int Average::getCount(void) {
return count;
}
double Average::getTotal(void) {
return total;
}
double Average::getAverage(void) {
return (total/(double)count);
}
$ g++ -fpic -shared average.cpp -o average.so
使用共享库
showaverage.cpp
#include <iostream>
#include "average.h"
int main(int argc, char *argv[]) {
Average avg;
avg.insertValue(30.2);
avg.insertValue(88.8);
avg.insertValue(3.002);
avg.insertValue(11.0);
std::cout << "Average=" << avg.getAverage() << "\n";
return 0;
}
$ g++ showaverage.cpp ./average.so -o showaverage
:~$ ./showaverage
Average=33.2505
混合编译C和C++程序
C语言使用简单函数名,不考虑参数的个数和类型;而C++函数总将它的参数类型列表当作函数名的一部分。
在C++中调用C
csayhello.c
#include <stdio.h>
void csayhello(const char *str) {
printf("%s\n", str);
}
cpp2c.cpp
#include <iostream>
extern "C" void csayhello(const char *str); // c++中需使用extern "C"声明C函数原型
int main(int argc, char *argv[]) {
csayhello("Hello from cpp to c");
return 0;
}
$ g++ -c cpp2c.cpp
$ gcc -c csayhello.c
$ gcc cpp2c.o csayhello.o -lstdc++ -o cpp2c
或者
$ gcc cpp2c.cpp csayhello.c -lstdc++ -o cpp2c
错误方式如下
$ g++ cpp2c.cpp csayhello.c -o cpp2c // 出错信息cpp2c.cpp:(.text+0x11): undefined reference to `csayhello'
在C中调用C++
cppsayhello.cpp
#include <iostream>
extern "C" void cppsayhello(char *str); // c++中虽然用extern "C"指出为C函数,但实现在.cpp中,函数内部实际为C++代码
void cppsayhello(char *str) {
std::cout << str << "\n";
}
c2cpp.c
int main(int argc, char *argv[]) {
cppsayhello("Hello from C to C++");
return 0;
}
$ gcc cppsayhello.cpp c2cpp.c -lstdc++ -o c2cpp
错误方式如下
$ g++ cppsayhello.cpp c2cpp.c -o c2cpp // 出错信息c2cpp.c:2:35: error: ‘cppsayhello’ was not declared in this scope
GCC: The Complete Reference——Arthur Griffith,胡恩华译