简书收藏--编译原理程序员今日看点

知识回顾 - C/C++ 编译常识

2016-09-25  本文已影响792人  yche

前言


本文主要参考 Compiling Cpp - By Ubuntu,并经过运行测试完成。要自行测试请安装好Gcc,:),windows下面用cygwin装一下,mac下用包管理器homebrew装一下,linux下应该默认已经安装了gcc。

C/C++程序的编译其实主要有几个阶段:1.编译预处理(包括include头文件的复制,宏定义的展开处理等等),2.把预处理后的文件经过语法(syntax)分析和语义(sematics)分析之后生成汇编文件,3.然后再利用汇编器和链接器(链接上动态链接库,插入静态链接库代码等等)生成可执行文件,比如Linux下就是elf格式的文件。

本文所用的指令(可执行程序)为g++,而非gcc,因为g++自动链接了C++标准库的动态链接库libstdc++.so,就像gcc自动链接了C标准库的动态链接库libglibc.so一样。

Gcc这个编译工具的一些常识


然后在实际写小程序编译时候,如果编译器版本大于gcc-4.8,我建议大家加上compiler flag -std=c++11;如果编译器版本大于gcc5.x,我建议大家加上compiler flag -std=c++14,因为最近看到一个youtube的视频 C++之父在CppCon2016上的KeyNote The Evolution of C++ Past, Present and Future , C++之父说目前建议大家学习C++的baseline至少为C++11标准。

另外请加上-wall这个compiler flag,让编译器报出所有可能的警告;然后如果你需要获得Simd(single instruction multiple data)俗称向量话还有其他的一些编译优化效果的话请加上-O3;然后如果你要调试程序的话,请加上-g,保留符号表信息。

C/C++编译涉及的文件命名规范


这一部分参考了 Compiling Cpp - By Ubuntu 这篇Wiki的内容。文件命名规范如所示(包括文件后缀名,文件类型和补充说明):

Gcc-生成可执行文件-给个直观感受(Hello-World)


#include <iostream>
int main(int argc,char *argv[])
{
    std::cout << "hello, world" << std::endl;
    return(0);
}
mkdir -p build
g++ hello_world.cpp -o build/hello_world_elf_file
chmod +x build_hello_world.sh
./build_hello_world.sh
build/hello_world_elf_file 
hello, world

Gcc-编译预处理(Hello-World)


g++ -E hello_world.cpp -o hello_world.ii

引用下 Compiling Cpp - By Ubuntu 的一段话:

选项 -E 使 g++ 将源代码用编译预处理器处理后不再执行其他动作。
本文前面所列出的 helloworld.cpp 的源代码,仅仅有六行,而且该程序除了显示一行文字外什么都不做,但是,预处理后的版本将超过 1200 行。这主要是因为头文件 iostream 被包含进来,而且它又包含了其他的头文件,除此之外,还有若干个处理输入和输出的类的定义。

grep -c "" build/hello_world.ii

Gcc-生成汇编代码(Hello-World)


引用下 Compiling Cpp - By Ubuntu 的一段话:

选项 -S 指示编译器将程序编译成汇编语言,输出汇编语言代码而後结束。下面的命令将由 C++ 源码文件生成汇编语言文件 helloworld.s:

g++ -S hello_world.cpp -o build/hello_world.s

Gcc-处理多个源文件(Multiple-File-Hello-World)


#include <iostream>
class SayUtil
{
    public:
        void sayStr(const char *);
};
#include "hello_world_util.h"
void SayUtil::sayStr(const char *str)
{
    std::cout << str << "\n";
}
#include "hello_world_util.h"
int main(int argc, char const *argv[]) {
  SayUtil say_util;
  say_util.sayStr("hello, world");
  return 0;
}
mkdir -p build
g++ -c hello_world_util.cpp -o build/hello_world_util.o
g++ -c hello_world_main.cpp -o build/hello_world_main.o
#link and generate executable
g++ build/hello_world_util.o build/hello_world_main.o -o build/multiple_file_hello_world0
#remove intermediate files
rm build/hello_world_util.o build/hello_world_main.o
mkdir -p build
g++ hello_world_main.cpp hello_world_util.cpp -o build/multiple_file_hello_world1

Gcc-创建静态链接库


引用下 Compiling Cpp - By Ubuntu 的一段话:

静态库是编译器生成的一系列对象文件的集合。链接一个程序时用库中的对象文件还是目录中的对象文件都是一样的。库中的成员包括普通函数,类定义,类的对象实例等等。静态库的另一个名字叫归档文件(archive),管理这种归档文件的工具叫 ar 。

在讲静态链接库之前,我主要先提一下几个注意点:

然后就是例子中要用到的4个文件了, 其中前三个会编译成.o文件,然后通过ar打包到静态链接库,另一个是包含主方法的文件:

#include <iostream>
extern void sayhello(void);

class Say {
private:
  char *string;

public:
  Say(char *str) { string = str; }
  void sayOther(const char *str) {
    std::cout << str << " from \"" << string << "\"" << std::endl;
  }
  void sayString(void);
};

#include "say_util.h"
void Say::sayString() { std::cout << string << std::endl; }

// For built-in-library usage
Say librarysay("Library instance of Say");

#include <iostream>
void sayhello() { std::cout << "hello from a static library" << std::endl; }

#include "say_util.h"

int main(int argc, char *argv[]) {
  //告诉编译器这个Say类型的变量(symbol)
  // librarysay在其他的编译单元中,可作为全局变量使用
  extern Say librarysay;
  Say localsay = Say("Local instance of Say");
  sayhello();
  librarysay.sayOther("say Something from librarysay");
  librarysay.sayString();
  localsay.sayString();
  return (0);
}

mkdir -p build
g++ -c say_util.cpp -o build/say_util.o
g++ -c say_hello_func.cpp -o build/say_hello_func.o
#程序 ar 配合参数 -r 创建一个新库 libsay.a 并将命令行中列出的对象文件插入。
#采用这种方法,如果库不存在的话,参数 -r 将创建一个新的库,而如果库存在的话,将用新的模块替换原来的模块。
ar -r build/libsay.a build/say_util.o build/say_hello_func.o
g++ say_hello_main.cpp build/libsay.a -o build/say_hello_main
rm build/*.o

Gcc-创建动态链接库


引用下 Compiling C - By Ubuntu 的一段话:

共享库是编译器以一种特殊的方式生成的对象文件的集合。对象文件模块中所有地址(变量引用或函数调用)都是相对而不是绝对的,这使得共享模块可以在程序的运行过程中被动态地调用和执行。

选项 -o 用来为输出文件命名,而文件後缀名 .so 告诉编译器将对象文件链接成一个共享库。通常情况下,链接器定位并使用 main() 函数作为程序的入口,但是本例中输出模块中没有这种入口点,为抑制错误选项 -shared 是必须的。

 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./build/
mkdir -p build
#添加-fpic 这个compiler flag,告诉编译器,
#生成的对象模块采用浮动的(可重定位的)地址。缩微词 pic 代表“位置无关代码”(position independent code)
g++ -c say_util.cpp -fpic -o build/say_util.o
g++ -c say_hello_func.cpp -fpic -o build/say_hello_func.o

g++ -shared build/say_util.o  build/say_hello_func.o -o build/libsay.so
rm build/*.o
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./build/

g++ say_hello_main.cpp build/libsay.so -o build/say_hello_main_with_so

最后:我的实验代码和脚本Github链接


参考文章


上一篇下一篇

猜你喜欢

热点阅读