在嵌入式与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

__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 是进程模型核心
  • 位运算是嵌入式基本功
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐