分离编译模式简介

示例代码编译运行环境:Windows 64bits+VS2017+Debug+Win32。


1.分离编译模式的定义

分离编译模式源于C语言,在C++语言中继续沿用。简单地说,分离编译模式是指一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件(obj文件),最后将所有目标文件连接起来形成单一的可执行文件的过程。

2.分离编译模式的由来

分离编译模式是C/C++组织源代码和生成可执行文件的方式。在实际开发大型项目的时候,不可能把所有的代码都写在一个文件中,而是分别由不同的程序员开发不同的模块,再将这些模块汇总成为最终的可执行程序。

这里就涉及到不同的模块(源文件)定义的函数和变量之间的相互调用问题。C/C++语言所采用的方法是:只要给出函数原型(或外部变量声明),就可以在本源文件中使用该函数(或变量)。每个源文件都是独立的编译单元,在当前源文件中使用但未在此定义的变量或者函数,就假设在其他的源文件中定义好了。每个源文件生成独立的目标文件,然后通过连接(Linking)将目标文件组成最终的可执行文件。

程序编译过程包括预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和连接(Linking)。

3.分离编译模式的的要点

理解分离编译模式要注意以下几点。

(1)每个函数或外部变量(全局变量)只能被定义一次,但可以被多次“声明”。 考察如下程序。

#include <iostream>
using namespace std;
void func();
void func();
void func()
{
    cout<<”This ia a demo”<<endl;
}

int main()
{
    func();
}

函数func()被多次声明,并不影响程序的正常编译和运行。在一个源文件中允许同时包含定义和声明同一个标识符的语句,这样可以通过前置申明做到先使用后定义。

(2)函数声明也是有作用域的。 类的成员函数只能在类体中声明。对于外部函数,如果是在一个函数体内声明另一个外部函数,那么该函数声明的作用域就是从声明处开始到函数体结束为止。在别的位置要调用这个函数,需要再次声明。

如下面的程序,由两个源文件组成,a.cpp和b.cpp。函数func()定义在a.cpp中,b.cpp中有两个函数show()和main()都调用了a.cpp中定义的函数func()。如果坚持将函数声明放在函数体内部,则在函数show()和main()中必须分别对函数func()进行声明,否则编译出错。程序如下:

/***a.cpp***/
#include <iostream>
Using namespace std;
void func()
{
    cout<<”This is a demo”<<endl;
}
/***end of a.cpp***/

/***b.cpp***/
void show()
{
    void func(); //func()的声明必不可少
    func();
}

int mian()
{
    void func(); // func()的声明必不可少
    func();
    show();
}
/***end of b.cpp***/

通常情况下,将外部函数或外部变量的声明放在.h头文件中。对于不在源文件中定义的函数(或变量),只要将相应的头文件通过#include指令包含进来,就可以正常使用了。

(3)一个函数被声明却从未定义,只要没有发生函数调用,编译连接是不会出错的。 参考如下程序。

#include <iostream>
using namespace std;
class Demo
{
public:
    void func1();
    void func2();
};
void Demo::func1()
{
    cout<<”This is a demo”<<endl;
}

int main()
{
    Demo obj;
    obj.func1();
}   

观察以上程序可以,类Demo的定义是不完整的,因为成员函数func2未完成定义,但是func2从未发生过调用,所以,函数只有申明没有定义在不发生函数调用的情况下是可以通过编译连接的。

从分离编译模式的角度来看,函数Demo::func2()有可能定义在别的源文件中,参考如下程序。

/***a.cpp***/   
#include <iostream>
using namespace std;
class Demo
{
public:
    void func1();
    void func2();
};
void Demo::func2()
{
    cout<<”This is func2”<<endl;
}   
/***end of a.cpp***/    

/***a.cpp***/
#include <iostream>
using namespace std;
class Demo
{
public:
    void func1();
    void func2();
};
void Demo::func1()
{
    cout<<”This is func1”<<endl;
}

int main()
{
    Demo obj;
    obj.func2();
}
/***end of b.cpp***/    

观察以上程序,类Demo有两个成员函数,它们分别在a.cpp和b.cpp源文件中实现。类Demo是被“分离“实现的。所以,分离编译模式关心的是函数的调用规范(函数原型),至于函数是否真正实现要到连接的时候才能被发现。

由分离编译模式也可以得出头文件的书写规范。头文件的目的是提供其他源文件中定义的,可以被当前源文件使用的内容(函数、变量等)的声明,所以头文件可能要多次被不同的源文件包含,因此一般都不在头文件中定义函数或外部变量,因为这样的头文件只能被包含一次。

在一个源文件中定义函数,在另一个源文件中调用该函数,是分离编译模式下十分普遍的现象,但是如果定义的不是一个普通函数,而是一个函数模板,可能会发生错误。关于模板的使用规范,参见模板与分离编译模式


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Kevin-ZhangCG

[ Java面试题 ]多线程篇

2687
来自专栏吴伟祥

Linux下的shell简介(三) 原

        shell的本意是“壳”的意思,其实已经很形象地说明了shell在Linux系统中的作用。shell就是围绕在Linux内核之外的一个“壳”程序...

903
来自专栏FreeBuf

Flask Jinja2开发中遇到的的服务端注入问题研究

0×00. 前言 作为一个安全工程师,我们有义务去了解漏洞产生的影响,这样才能更好地帮助我们去评估风险值。本篇文章我们将继续研究Flask/Jinja2 开...

2265
来自专栏Golang语言社区

Go语言Goroutine与Channel内存模型

Go语言内存模型规定了在一个goroutine中一个变量的读取的情况下,确保能够观察到在其他另外goroutine中写入同样变量的值。也就是说,如果在多个gor...

3506
来自专栏技术博文

Linux下各种压缩与解压

1、zip格式 压缩: zip -r [目标文件名].zip [原文件/目录名] 解压: unzip [原文件名].zip 注:-r参数代表递归 2、tar格...

3846
来自专栏Python

linux每日命令(22):find命令参数详解

文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。 可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。 不管当前...

1092
来自专栏Golang语言社区

Go语言Goroutine与Channel内存模型

Go语言内存模型规定了在一个goroutine中一个变量的读取的情况下,确保能够观察到在其他另外goroutine中写入同样变量的值。也就是说,如果在多个gor...

4454
来自专栏技巅

分布式日志收集系统: Facebook Scribe之结构及源码分析

1782
来自专栏JetpropelledSnake

RESTful源码笔记之RESTful Framework的Mixins小结

本篇对drf中的mixins进行简要的分析总结。 Mixins在drf中主要配合viewset共同使用,实现http方法与mixins的相关类与方法进行关联。

691
来自专栏九彩拼盘的叨叨叨

Node.js 版本管理器: nvm 介绍

有时候,我们需要测试写的 Nodejs 的程序在不同 Nodejs 版本下是否能正常运行;或是我们想要尝试下最新版 Nodejs 的新特性,但常用的代码需要旧版...

871

扫码关注云+社区

领取腾讯云代金券