嵌入式之C/C++(五)函数
本文总结了嵌入式与Linux C/C++面试中常见的函数相关问题。主要内容包括:1)使用__attribute__((constructor))让函数在main()前执行;2)虚函数表实现多态的机制,强调基类析构函数必须为虚函数;3)函数调用栈和select等系统调用的实现原理;4)进程相关的fork/exec函数;5)数组指针操作和位运算技巧。这些问题不仅考察语法知识,更注重对底层实现机制的理解

在嵌入式与Linux C/C++面试中,函数相关问题几乎必问。从“如何让函数在main之前执行”,到析构函数、虚函数表、函数调用栈、select、fork/exec,这些问题不仅考语法,更考底层理解。
1 如何让函数在main()之前执行?
1.1 使用__attribute__((constructor))
在GNU编译器(gcc)中,可以使用__attribute__设置函数属性。
void before() __attribute__((constructor));
void after() __attribute__((destructor));
constructor:在main()之前执行destructor:在main()之后执行
1.2 完整示例代码
#include <stdio.h>
void before() __attribute__((constructor));
void after() __attribute__((destructor));
void before()
{
printf("this is function %s\n", __func__);
}
void after()
{
printf("this is function %s\n", __func__);
}
int main()
{
printf("this is function %s\n", __func__);
return 0;
}
输出结果:
this is function before
this is function main
this is function after
2 __attribute__常见函数属性?
| 属性 | 作用 |
|---|---|
alias |
设置函数别名 |
aligned |
指定对齐方式 |
always_inline |
强制内联 |
constructor |
main 前执行 |
destructor |
main 后执行 |
format |
printf 格式检查 |
noreturn |
函数不返回 |
weak |
定义弱符号 |
2.1 noreturn 易错点
__attribute__((noreturn)) void fatal_error(void);
⚠️ 注意:
noreturn≠ 返回值是void- 而是 函数执行完后进程结束
- 如:
exit()、_exit()、abort()
2.2 weak弱符号
- 如果同时存在强符号和弱符号
- 优先使用强符号
- 常用于:
- 启动文件
- 默认回调函数
3 为什么析构函数必须是虚函数?
3.1 问题场景
Base* p = new Derived();
delete p;
如果 Base 的析构函数不是 virtual:
❌ 只调用 Base::~Base()
❌ Derived 部分资源泄漏
3.2 正确做法
class Base {
public:
virtual ~Base() {}
};
📌 结论:
只要类可能被继承,就必须定义虚析构函数
4 为什么C++默认析构函数不是虚函数?
原因只有一个:性能与空间
- 虚函数需要:
- 虚函数表(vtable)
- 虚表指针(vptr)
- 增加对象体积
- 增加一次间接寻址
📌 设计哲学:
只有需要多态的类,才付出虚函数的代价
5 析构函数的作用是什么?
核心作用:资源回收
- 文件句柄
- 内存
- 锁
- socket
~File()
{
fclose(fp);
}
📌 面试标准答案:
析构函数用于在对象生命周期结束前,自动完成清理工作,实现 RAII。
6 静态函数 vs 虚函数?
| 对比 | 静态函数 | 虚函数 |
|---|---|---|
| 绑定时机 | 编译期 | 运行期 |
| 是否多态 | ❌ | ✅ |
| 是否使用 vtable | ❌ | ✅ |
| 调用开销 | 小 | 略大 |
📌 一句话总结:
静态函数快,虚函数灵活。
7 重载(Overload)和覆盖(Override)的区别?
| 项目 | 重载 | 覆盖 |
|---|---|---|
| 关系 | 同一类 | 父子类 |
| 决定因素 | 参数列表 | 对象类型 |
| 绑定 | 编译期 | 运行期 |
📌 口诀:
重载看参数,覆盖看对象。
8 虚函数表如何实现运行时多态?
8.1 虚函数表是什么?
- 类的虚函数地址表
- 每个对象包含一个
vptr - 指向所属类的
vtable
8.2 多态调用过程?
Base* p = new Derived();
p->func();
- 通过
p找到vptr - 通过
vtable找到函数地址 - 调用
Derived::func()
9 C 语言函数调用是如何实现的?
9.1 栈帧结构
函数调用依赖 栈(stack):
- 参数传递
- 返回地址
- 局部变量
- 保存寄存器
9.2 关键寄存器(x86)
esp:栈指针ebp:帧指针
📌 每个函数都有独立的栈帧
10 select 函数?(Linux I/O 多路复用必考)
10.1 函数原型
int select(int maxfdp,
fd_set *readfds,
fd_set *writefds,
fd_set *errorfds,
struct timeval *timeout);
10.2 select 特点总结
- ✅ 优点:
- 跨平台
- 支持微秒级超时
- ❌ 缺点:
- fd 上限 1024
- 轮询扫描
- 用户态 ↔ 内核态频繁拷贝
- 水平触发
📌 面试总结:
fd 多时性能差,是 select 的致命问题。
11 fork / wait / exec ?
① fork
- 创建子进程
- 写时拷贝(COW)
- 父返回子 pid,子返回 0
② exec
- 替换当前进程映像
- 成功无返回
- 失败返回 -1
③ wait
- 父进程阻塞
- 等待子进程状态改变
12 数组与指针?
*(a[1] + 1)
*(&a[1][1])
(*(a + 1))[1]
👉 三者等价:a[1][1]
📌 本质:数组名 + 偏移 + 解引用
12.1 数组下标可以为负数吗?
✅ 可以
int *p = &a[4];
p[-1] == a[3];
⚠️ 合法但危险,需确保地址有效
13 位操作面试题?
13.1 统计二进制 1 的个数
while(x) {
x &= (x - 1);
count++;
}
📌 每次消除最低位的 1
13.2 交换变量(不用第三个变量)
a ^= b;
b ^= a;
a ^= b;
13.3 设置 / 清除 bit3
#define BIT3 (1 << 3)
a |= BIT3; // set
a &= ~BIT3; // clear
14 面试总结
- constructor 可让函数在 main 前执行
- 父类析构函数必须为 virtual
- 虚函数表实现运行时多态
- select 本质是轮询
- fork + exec 是进程模型核心
- 位运算是嵌入式基本功
更多推荐



所有评论(0)