C++ 编译与链接面试题
1. C++程序的编译过程是什么?
答案:
- 四个阶段预处理(Preprocessing)处理#include、#define等指令展开宏条件编译生成.i文件编译(Compilation)将预处理后的代码转换为汇编代码语法检查、语义分析优化生成.s文件汇编(Assembly)将汇编代码转换为机器码生成目标文件.o或.obj链接(Linking)将多个目标文件链接成可执行文件解析符号引用重定位生成可执行文件
- 命令示例
# 预处理 g++ -E main.cpp -o main.i # 编译 g++ -S main.cpp -o main.s # 汇编 g++ -c main.cpp -o main.o # 链接 g++ main.o -o main
C++面试题合集 : https://www.nowcoder.com/creation/manager/columnDetail/MJ4oG8
2. 什么是头文件?为什么需要头文件保护?
答案:
- 头文件作用声明函数、类、变量提供接口代码复用
- 头文件保护防止重复包含避免重定义错误
- 传统方法(Include Guard)
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
// ...
};
#endif
- 现代方法(#pragma once)
#pragma once
class MyClass {
// ...
};
- 对比pragma once更简洁Include Guard更标准现代编译器都支持#pragma once
3. 什么是链接?静态链接和动态链接的区别?
答案:
- 链接定义将多个目标文件组合成可执行文件解析符号引用分配内存地址
- 静态链接编译时将库代码复制到可执行文件生成独立的可执行文件文件大,但不依赖外部库Windows: .lib, Linux: .a
- 动态链接运行时加载库多个程序共享同一库文件小,节省内存需要库文件存在Windows: .dll, Linux: .so
- 对比
# 静态链接 g++ main.cpp -static -o main # 动态链接(默认) g++ main.cpp -o main
- 优缺点静态:独立、快速启动,但文件大动态:节省空间、易更新,但依赖外部库
4. 什么是符号?extern关键字的作用?
答案:
- 符号定义函数名、变量名等标识符链接器通过符号解析引用
- extern关键字声明外部符号不分配内存告诉编译器符号在其他文件定义
- 使用示例
// file1.cpp
int globalVar = 10;
void func() {
// ...
}
// file2.cpp
extern int globalVar; // 声明
extern void func(); // 声明
void use() {
cout << globalVar;
func();
}
- extern "C"使用C链接方式避免C++名称修饰用于C/C++混合编程
extern "C" {
void c_function();
}
5. 什么是名称修饰(Name Mangling)?
答案:
- 定义编译器将函数名编码包含参数类型、命名空间等信息支持函数重载
- 示例
void func(int); void func(double); void func(int, int); // 编译后可能变成 // _Z4funci // _Z4funcd // _Z4funcii
- 查看修饰名
# Linux nm main.o # Windows dumpbin /symbols main.obj
- 问题不同编译器修饰规则不同导致二进制不兼容
- 解决方法使用extern "C"使用相同编译器使用标准ABI
6. 什么是链接错误?常见的链接错误有哪些?
答案:
- 未定义引用(Undefined Reference)声明了但未定义忘记链接库拼写错误
undefined reference to `func()'
- 重复定义(Multiple Definition)同一符号定义多次头文件中定义函数
multiple definition of `globalVar'
- 解决方法
// 错误:头文件中定义 // header.h int globalVar = 10; // 错误 // 正确:头文件中声明 // header.h extern int globalVar; // source.cpp int globalVar = 10;
- inline函数可以在头文件中定义不会导致重复定义
7. 什么是编译单元?
答案:
- 定义一个.cpp文件及其包含的所有头文件编译器独立编译每个编译单元生成一个目标文件
- 示例
main.cpp #include "a.h" #include "b.h" // 编译单元包含:main.cpp + a.h + b.h
- 影响编译时间头文件修改会重新编译所有包含它的编译单元前向声明可以减少依赖
- 优化减少头文件包含使用前向声明使用Pimpl惯用法
8. 什么是前向声明?什么时候使用?
答案:
- 定义声明类或函数,不提供完整定义告诉编译器类型存在
- 使用场景
// 前向声明
class B;
class A {
B* ptr; // 指针,可以使用前向声明
// B obj; // 错误,需要完整定义
void func(B& b); // 引用,可以使用前向声明
};
// B的完整定义
class B {
// ...
};
- 优势减少编译依赖加快编译速度避免循环依赖
- 限制只能用于指针和引用不能访问成员不能创建对象
9. 什么是Pimpl惯用法?
答案:
- 定义Pointer to Implementation将实现细节隐藏在指针后减少编译依赖
- 实现
// widget.h
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // 前向声明
unique_ptr<Impl> pImpl;
};
// widget.cpp
class Widget::Impl {
public:
void doSomething() {
// 实现
}
private:
// 私有成员
int data;
vector<int> vec;
};
Widget::Widget() : pImpl(make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() {
pImpl->doSomething();
}
- 优势隐藏实现细节减少编译依赖二进制兼容性加快编译速度
- 缺点额外的间接访问内存分配开销代码复杂度增加
10. 如何优化编译时间?
答案:
- 减少头文件包含使用前向声明只包含必要的头文件在.cpp中包含,而非.h
- 使用预编译头文件
// stdafx.h #include <iostream> #include <vector> #include <string> // 编译 g++ -x c++-header stdafx.h -o stdafx.h.gch
- 并行编译
# Make make -j4 # CMake cmake --build . -j4
- 使用ccache缓存编译结果加速重复编译
- 模块化设计减少依赖使用接口类Pimpl惯用法
- 增量编译只编译修改的文件使用构建系统(Make、CMake)
- 编译器优化
C++面试总结 文章被收录于专栏
本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。
