首页 > 技术文章 > 语言基础(4):文件编译原理

wnwin 2017-10-13 10:46 原文

1、头文件和源文件应该写什么

头文件和源文件写什么,原则取决于C++编译机制,C++语言以"xxx.cpp"为单位进行编译生成 xxx.obj, 然后目标文件链接生成 xxx.exe (windows),类似 xxx.h 文件是不参与编译,头文件主要通过 #include 宏,将头内容复制到源文件。所以如果在头文件定义了一个变量或这函数并且头文件被重用(多处包含),此时编译就会报出“重定义”!

因此,C++头文件应该写的内容是变量和函数的声明,注意是声明!!!而不能是定义(例外见下一节)。源文件中写变量和函数的定义,当然也可以在源文件中同时写声明和定义(此时变量或函数为该源文件私有,在其他文件使用时,需要使用extern声明)。

举个栗子

// test.h
#ifndef _TEST_H_
#define _TEST_H_
 
#include<string>

using namespace std;

string str;
extern void show();
 
#endif

// test.cpp
#include "test.h"
#include<iostream>
#include<string>
 
str = "Hi, I'm test!"
void show()
{
    cout<< str << endl;
}


#2、头文件例外情况 ##2.1 static变量  static变量具有内链接属性,所以对外不文件不可见,不会产生命名冲突。值得一提 const 变量有时也在头文件中定义,这是因为 const 变量也是内链接的,但如果是用 extern const组合修饰,则变量不再是内链接的。此外,修饰组合 static const 常用来修饰文件内的常量。

2.2 inline函数

inline函数默认也是内链接属性的,所以不会产生函数重定义,当然使用extern inline组合修饰,就不能放在头文件了。

2.3 类定义

为什么头文件可以定义类,而不会产生重命名,因为类定义具有内链接属性,这和变量声明、函数声明并无区别,除此之外 它属于类型定,如果说其他文件里重复定义类,分别取包含别亦,亦不会产生冲突(所以命名空间很重要啊)。

根本来讲,之所以存在链接属性、以及上面三种例外,遵顼基本的原则: "在头文件中定义这些实体,是因为编译器需要它们的定义(不只是声明)来产生代码"。

PS:我们知道源文件编译会生成符号表,其实包含了两张表:
未解决符号表

提供了在该编译单元引用,但是定义不在本编译单元的符号的地址(拥有者是编译单元)

导出符号表

提供了本编译单元具有定义,而且可以提供给其他编译单元使用的符号和地址(拥有者是编译单元)。

而所谓内链接属性就是不在导出符号表中的变量、函数或者类对应的符号,相对应的就是外链接属性,不再赘述。

对应存在一个“单一定义规则”(One-Definition Rule, ODR)。根据此规则, 如果对同一个类的两个定义完全相同且出现在不同编译单位,会被当作同一个定义,其实这个规则是个表象的陈述,原理如上所揭示。


3、避免符号重定义的办法

  • 使宏变量标识(c++的语法)
#ifndf _XXX_H_
#define _XXX_H_
 
//content
 
#endif

缺点:本方法可能会产生宏名称"撞车",从而产生漏包含一些头文件中定义的内容,最终产生缺少定义错误!

  • 使用 #program once(编译器支持)

缺点:本方法是针对文件物理上的只编译一次,如果两个不同名的文件,保存了相同内容并且均包含在项目中,则会出现重定义错误。不过比起 1,这个错误更易排查,所以使用更广,但是这个宏只属于VC编译器,不在标准定义范围。

推荐阅读