GCC编译C/C++

编译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,胡恩华译

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值