C/C++文件

C/C++程序文件包括 .h .c .hpp .cpp,其中源文件(.c .cpp)是基本的编译单元,头文件(.h .hpp)不会被编译器编译。

C/C++项目构建(build)过程,分为以下几个步骤 预处理 → 编译 → 链接。

预编译

预编译的过程可以理解为编译器(实际上是预处理器,这里统称为编译器就可以了)在正式编译之前处理C/C++文件中的预处理命令,即#开头的代码。

常用的几个预处理命令如下:

#include ......

#ifdef ...... #else......#endif

#define ......

#pragma ......

举个例子,下面是个很简单的类定义:

MyClass.h

#define DEFAULT_VALUE 0

class MyClass {
public:
void Fun();
public:
int value = DEFAULT_VALUE;
};

MyClass.cpp

#include "MyClass.h"

void MyClass::Fun() {
// Do someting
return;
}

预编译完成后的样子:

class MyClass {
public:
    void Fun();
public:
    int value = ;
}; void MyClass::Fun() {
    // Do someting
    return;
}

可以看到编译器把.h文件替换到了.cpp文件中的#include 位置上,把DEFAULT_VALUE定义的值也替换到了相应的位置。

编译

预编译之后,编译器会编译每个源文件(.c .cpp),如果编译成功,会生成对应的目标文件,Linux为.o文件,Windows平台下为.obj文件。

以Linux平台为例,上面的MyClass.cpp编译完成后会生成MyClass.o文件

使用objdump可以看到目标文件MyClass.o的内容

$$ objdump -x MyClass.o

......
0000000000000000 g     F .text  0000000000000015 _ZN7MyClass3FunEv
......

编译器会把MyClass::Fun()的名字改成_ZN7MyClass3FunEv,这个过程叫Mangle,由于C++支持重载,覆盖等特性,所以编译器必须把函数用一个唯一的标识表示。这个字符串就是编译器生成的唯一标识。

这里还要单独说一下头文件,头文件的既然不是编译单元,那么它的作用是什么?

头文件就是负责”声明“,编译器在编译MyClass.cpp的时候,对于MyClass这个类以及Fun()这个成员函数,编译器必须找到它的声明,这个函数才能被正确编译。

如果有其他cpp需要使用MyClass这个类的时候,也需要它的的声明。例如

main.cpp

#include "MyClass.h"

int main(int argc, char** argv) {
MyClass tmp;
tmp.Fun();
return ;
}

加上#include "MyClass.h" 编译器在编译main.cpp的时候才知道怎么编译MyClass这个类。MyClass.h里声明是不会真正被编译到main.o中,.h文件中的内容在目标文件中只是以列表的形式存在,这个表在后面链接时会用到。

当然,头文件不仅可以用来声明,还可以定义(定义全局变量,全局函数等),在头文件中的定义要小心,可能会引起链接错误。

链接

链接就是将一堆目标文件加静态库文件装配成可执行文件的过程。(或者是装配成静态/动态库的过程)

上面两个cpp分别被编译成了MyClass.o, 和main.o,我们要生成可执行程序的话,就必须经过链接的过程,把两个目标文件合成一个可执行文件。

main.o中,main函数会构造MyClass, 并且调用Fun()函数,那么main就根据MyClass.h生成的表,找到MyClass.o中的函数,这个就是链接器要做的工作。

常见错误

构建c/c++工程的时候,最常见的就是两种错误:

-- 编译错误,在编译过程中产生的错误,通常是语法错误,没有声明,重复声明导致编译目标文件错误

其中没有声明通常是由于没有#include相应的头文件,或者头文件缺少相应的声明。

而重复声明通常是#include了相同的头文件,比如 B.h 和 C.h 都包含 A.h,然后 main.h 包含了 B.h 和 C.h,这就导致A.h 在main中被包含了两次。

解决这个问题的方法是可以在所有.h文件的第一行加上

#pragma once

或者,使用#ifndef ... #define ... #endif 语句块

#ifndef NEWCLASS_H
#define NEWCLASS_H ...... #endif /* NEWCLASS_H */

-- 链接错误,常见的错误也是两种,没有定义和重复定义,和上面的没有声明,重复声明类似。(这里定义指的就是函数实现)

  • 先讨论没有定义(undefined reference to xxx)

通常是因为函数有声明,而且被使用了,但是没有被定义。比如上面MyClass.cpp中,如果Fun()没有被实现的话,MyClass.cpp和main.cpp编译时都不会报错,但是链接时会报告找不到Fun()。

当然,如果Fun()没被main.cpp调用的话,即使不实现它,整个构建过程也不会出错,因为链接器根本不会去找这个函数的定义。

  • 然后是重复定义(multiple definition)

指的一份相同的定义在两个目标文件中都存在,链接的时候链接器不知道时用哪个了。这种问题通常由于全局函数,和全局变量定义在了头文件中。导致多个目标文件包含相同的全局函数和全局变量的定义。

解决方法就是在头文件中声明,定义放到cpp文件中,或者为定义加上const 或 static这样的修饰符,链接时会对这些带有const和修饰符的变量特殊处理的。

const只适用于定义常量变量,static定义的是静态全局变量,只在当前cpp有效,所以链接它也不会被别的目标文件链接,就不会有重复定义的问题了。

总之在头文件中定义变量和函数要特别主意,可能会导致链接错误。

当然也不是所有定义都不能放到头文件中,比如刚才说的const常量,static全局变量就是例外,还有内联函数,可以定义在.h文件中,因为内联函数会被拷贝到每个目标文件中,也不会参与链接的过程。

还有模板类必须放在头文件中定义,这个下面会讨论这个。

关于模板,静态成员变量

模板类模板函数必须声明和定义在头文件中,原因是什么,举个例子,假设MyClass是模板类

MyClass.h

template <typename T>
class MyClass {
public:
void Fun();
public:
T value;
};

MyClass.cpp

#include "MyClass.h"

template <typename T>
void MyClass<T>::Fun() {
// Do someting
return;
}

main.cpp

int main(int argc, char** argv) {
MyClass<int> tmp;
tmp.Fun();
return ;
}

编译的时候没有问题,但是链接时会报错,main.cpp找不到MyClass<int>::Fun(),如下图

MyClass虽然定义了Fun函数,但是MyClass.o中存在MyClass<T>::Fun(),而根据MyClass.h文件,main.o中需要找到MyClass<int>::Fun()的定义

结果链接器哪都找不到,只好报错了。(实际上通过objdump查看MyClass.o,编译器都没有生成MyClass<T>::Fun(),因为编译器认为这个函数没人使用,就直接优化掉了)

如果非得在cpp中定义模板类的成员函数呢,有一种方法就是要显示的在cpp文件中声明,比如

MyClass.cpp

#include "MyClass.h"

template <typename T>
void MyClass<T>::Fun() {
// Do someting
return;
} template void MyClass<int>::Fun();

加上下面这行就不会有问题了,但是缺点就是开发MyClass的程序员无从知道其他类是怎么使用这个模板的,不可能把所有可能的模板参数全都一一的列在这里。

所以模板类的定义还是要写在.h文件中,

那么如果main.cpp使用到了MyClass<int>, 另外一个cpp也使用到了MyClass<int>,会不会产生重复代码导致重复定义呢,不会,编译器会处理好模板类的。

下面是静态成员变量,为什么静态成员变量的定义要放在cpp里,(模板类的静态成员变量除外)

静态成员变量和静态全局成员变量不同。

静态成员变量的作用域可以是整个工程,而静态全局变量的作用域只是当前的cpp。所以静态成员变量定义在.h中就会发生重定义错误。

C/C++ 的编译和链接的更多相关文章

  1. u-boot的配置、编译及链接

    第一次写技术博客,还有些兴奋呢.我是CrazyCatJack,大家可以叫我CCJ或者疯猫.我即将成为一名嵌入式Linux的驱动工程师,现在还是一枚大四狗,呼呼~大学期间做了一些项目和比赛,都是基于32 ...

  2. 原创 C++应用程序在Windows下的编译、链接:第三部分 静态链接(二)

    3.5.2动态链接库的创建 3.5.2.1动态链接库的创建流程 动态链接库的创建流程如下图所示: 在系统设计阶段,主要的设计内容包括:类结构的设计以及功能类之间的关系,动态链接库的接口.在动态链接库中 ...

  3. 原创 C++应用程序在Windows下的编译、链接:第二部分COFF/PE文件结构

    2.1概述 在windows操作系统下,可执行文件的存储格式是PE格式:在Linux操作系统下,可执行文件的存储格式的WLF格式.它们都是COFF格式文件的变种,都是从COFF格式的文件演化而来的. ...

  4. 原创 C++应用程序在Windows下的编译、链接:第一部分 概述

    本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 cl.exe是windows平台下的编译器,link.ex ...

  5. 拓展:使用终端创建、编译、链接OC…

    本文介绍一下如何使用Mac OS X自带终端快速创建.编译.链接OC程序. 1.打开终端 顺序:打开Finder——应用程序——实用工具——终端 2.打开需要存放 .m 文件的路径(比如我需要放到桌面 ...

  6. 【温故Delphi】之编译与链接(转)

    本文基本转自“Delphi编译/链接过程” @瓢虫Monster,向瓢虫君学习,:) 下图展示了Delphi是怎样编译源文件,并把它们链接起来,最终形成可执行文件的. 当Delphi编译项目(Proj ...

  7. App开发流程之数据持久化和编译静态链接库

    先记录数据持久化. iOS客户端提供的常用数据持久化方案:NSUserDefaults代表的用户设置,NSKeydArchiver代表的归档,plist文件存储,SQLite数据库(包括上层使用的Co ...

  8. C/C++程序从编译到链接的过程

    编译器是一个神奇的东西,它能够将我们所编写的高级语言源代码翻译成机器可识别的语言(二进制代码),并让程序按照我们的意图按步执行.那么,从编写源文件代码到可执行文件,到底分为几步呢?这个过程可以总结为以 ...

  9. Linux环境下的编译,链接与库的使用

    参考博客: http://www.cnblogs.com/qytan36/archive/2010/05/25/1743955.html http://m.blog.csdn.net/article/ ...

  10. extern "C"——用“C”来规约在C++中用C的方式进行编译和链接

    C++中的extern “C”用法详解     extern "C"表明了一种编译规约,其中extern是关键字属性,“C”表征了编译器链接规范.对于extern "C& ...

随机推荐

  1. Django extend(继承)模板标签

    在 views.py 上修改 ... def ordered(req): return render(req, "ordered.html") def shopping_car(r ...

  2. 嵌入式linux开发uboot启动内核的机制(二)

    一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: ...

  3. Nginx下HTML页面POST请求静态JSON数据返回405状态

    在浏览器访问HTML页面,发现一些静态JSON数据没有显示,F12查看,如下图所示: 可以看到请求方式为POST 将请求链接复制在浏览器地址栏访问,可以正常请求到数据 F12查看,可以看到请求方式为G ...

  4. JDK1.8 LocalDate 使用方式;LocalDate 封装Util,LocalDate工具类(三)

    未完待续 ........ 前言: 大企鹅的日常分享,第三步,最近一直在想策略设计模式和工厂模式结合优化ifelse的写法,看了很多资料,终于写出了自己要写的东西,在这段时间里,也有求助小伙伴,但是, ...

  5. awesome-RecSys

    https://github.com/jihoo-kim/awesome-RecSys?fbclid=IwAR1m6OebmqO9mfLV1ta4OTihQc9Phw8WNS4zdr5IeT1X1OL ...

  6. nginx lnmp之nginx+php

    配置如下(在server部分添加): location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/tmp/php-fcgi.sock; ...

  7. Spring Boot 知识笔记(Filter过滤器)

    Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 htm ...

  8. 第01组 Alpha冲刺(5/6)

    队名:007 组长博客: https://www.cnblogs.com/Linrrui/p/11901035.html 作业博客: https://edu.cnblogs.com/campus/fz ...

  9. 在 Debian 上的 SQL Server 的安裝指引

    我想在 linux 环境下尝试一下 Microsoft SQL Server,但是微软只发布了针对 Red Hat,SUSE,Ubuntu 和 Docker 引擎的.我平时习惯使用 Debian, U ...

  10. Java集合详解1:一文读懂ArrayList,Vector与Stack使用方法和实现原理

    本文非常详尽地介绍了Java中的三个集合类 ArrayList,Vector与Stack <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整 ...