Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

头文件中的宏定义 #48

Open
xcjthu opened this issue Feb 28, 2022 · 2 comments
Open

头文件中的宏定义 #48

xcjthu opened this issue Feb 28, 2022 · 2 comments

Comments

@xcjthu
Copy link
Member

xcjthu commented Feb 28, 2022

宏定义的好处之一:宏定义的使用可以增加预编译指令,防止多次包含同一头文件时出现编译错误。

// func.h
int ADD(int a, int b);

//以下为增加宏定义的写法
#ifndef FUNC_H
#define FUNC_H
int ADD(int a, int b);
#endif

结论:在头文件中使用宏定义,可以使得头文件在一个源文件中被多次include时,同一段代码只被编译一次。一个头文件在不同的源文件中,依旧会被编译多次。因此若在头文件中定义函数或变量,即使头文件中使用了宏定义,头文件中的代码在多个源文件中依旧会被编译多次,导致链接时出错。

我们从下面的样例中进行分析:

func1.h
int add(int x, int y){
  return x + y;
}
func2.h
#include "func1.h"
int add_2(int x, int y){
  return add(x, y) + 1;
}
main.cpp
#include <iostream>
#include "func1.h"
#include "func2.h"

using namespace std;
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

执行编译命令:g++ main.cpp -o main,编译出错:

In file included from main.cpp:2:
In file included from ./func2.h:1:
./func1.h:1:5: error: redefinition of 'add'
int add(int x, int y){
    ^
./func1.h:1:5: note: previous definition is here
int add(int x, int y){
    ^
1 error generated.

原理:#include 指令将被包含的文件代码直接复制到当前文件,即 main.cpp 中的 #include "func1.h"#include "func2.h"使得 main.cpp 等价为如下代码:

#include <iostream>

// #include "func1.h"
int add(int x, int y){
  return x + y;
}

// #include "func2.h"
int add(int x, int y){
  return x + y;
}

int add_2(int x, int y){
  return add(x, y) + 1;
}

// main.cpp
using namespace std;
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

因此,func1.h 在main.cpp中被重复包含,若无宏定义,则该段代码编译出错。宏定义即可解决该点问题,使得在main.cpp中func1.h的代码只被编译一次。即将func1.h修改为如下情况,则编译可以顺利通过

#ifndef FUNC1_H
#define FUNC1_H
int add(int x, int y){
  return x + y;
}
#endif

多个源文件包含func1.h:虽然宏定义解决上面单个文件中多次包含的问题,但依旧需要避免在头文件中进行变量定义及函数定义。

保留上述func1.h的实现不变,定义func1.cpp并修改main.cpp如下:

func1.h

#ifndef FUNC1_H
#define FUNC1_H
int add(int x, int y){
  return x + y;
}
#endif

func1.cpp

#include "func1.h"
int add_2(int x, int y){
  return add(x, y) + 1;
}

main.cpp

#include "func1.h"
#include <iostream>
using namespace std;
int add_2(int x, int y);
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

执行编译命令g++ func1.cpp main.cpp -o main,编译报错如下

duplicate symbol 'add(int, int)' in:
    /var/folders/2k/q001fz5n70j17wpl5v5lltxw0000gn/T/func1-3e6d02.o
    /var/folders/2k/q001fz5n70j17wpl5v5lltxw0000gn/T/main-80fcd6.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

报错原因就是,在func1.cpp与main.cpp中,add函数被重复定义。宏定义作用范围仅为当前文件,无法跨文件起作用。即func1.cpp中的#define FUNC1_H与main.cpp中的#define FUNC1_H不相关,分别仅在func1.cpp与main.cpp中有效。

因此,宏定义可以解决单个源文件中某段代码被重复定义带来的错误。无法解决在多个源文件中重复include的问题。

@EGalahad
Copy link

EGalahad commented Feb 28, 2022

In the last example, I instead tried 'compile and then link', e.g.

g++ -c main.cpp func1.cpp
g++ main.o func1.o -o main

and here is my log

/usr/bin/ld: func1.o: in function `add(int, int)':
func1.cpp:(.text+0x0): multiple definition of `add(int, int)'; main.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit s

and here is my reasoning for this issue:

When compiled to machine code *.o and then link, the linker does not know that add is actually defined in the same file, e.g. func1.h, thus causing a duplicate function definition.

My question is:

  1. Can I conclude that #ifndef or #pragma once lose their effect once it is compile into machine code? since if the still take effect the linker should be able to know add function has been defined in the first file.
  2. As this problem can be addressed by deleting #include "func1.h" in main.cpp, which is symple, why should we only declare functions in *.h files rather than defining them?

@hzhwcmhf
Copy link

@EGalahad

第一个问题:是的,#ifdef和#pragma once都是宏,所以只在编译期有效
第二个问题:.h文件提供了使用函数的声明,如果不在main.cpp里include func1.h,需要手动声明函数,否则编译器无法知道函数调用所对应的的类型。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants