海康威视 软件开发- C++ 一面

1、自我介绍

2、为什么实习只有两个月

3、structclass 的区别

答案:C++ 里 structclass 本质上都可以定义成员变量、成员函数、构造析构、继承和模板,能力上没有本质差别。最核心的区别主要有两个:

  • struct 默认访问权限是 public
  • class 默认访问权限是 private

还有一个区别是默认继承方式:

  • struct 默认是 public 继承
  • class 默认是 private 继承

所以很多时候它们的差别更偏风格。如果是简单的数据聚合类型,很多人习惯用 struct;如果更强调封装和对象语义,通常会用 class

代码:

struct A {
    int x;   // 默认 public
};

class B {
    int x;   // 默认 private
};

4、malloc / new / free / delete 的区别是什么

答案:mallocfree 是 C 里的内存管理函数,newdelete 是 C++ 里的运算符。它们最关键的区别不只是“一个分配内存,一个释放内存”,而是对象语义不同。

malloc 只是按字节申请一块原始内存,不会调用构造函数,返回 void*free 只是释放这块内存,不会调用析构函数。

new 在分配内存之后,还会调用构造函数完成对象初始化;delete 在释放内存之前,会先调用析构函数清理对象资源。

另外还有几个常见区别:

  • malloc 失败返回 nullptr
  • new 默认失败会抛出 std::bad_alloc
  • new[] / delete[] 要成对使用
  • malloc/freenew/delete 不能混用

代码:

#include <cstdlib>
#include <iostream>
using namespace std;

class Test {
public:
    Test() { cout << "construct\n"; }
    ~Test() { cout << "destruct\n"; }
};

int main() {
    Test* p1 = new Test;
    delete p1;

    Test* p2 = (Test*)malloc(sizeof(Test));
    free(p2);
}

5、开辟内存的实际大小和要求开辟的大小不一样,怎么去看

答案:这个问题本质上是在问:你申请了 1 字节、3 字节、17 字节,为什么实际分配往往不是这么精确。原因主要有两个:

  • 分配器本身会按固定粒度或对齐规则管理内存
  • 还要额外存管理信息,比如块头、链表指针、元数据等

所以用户申请大小和底层实际分配大小通常不一样。如果想观察这个现象,在 Linux 下常见有几种办法:

  • malloc_usable_size() 看 glibc 实际给你的可用大小
  • gdb 配合堆分析
  • mallinfo / mallinfo2
  • 用 jemalloc / tcmalloc 的工具接口看分配信息

代码:

#include <malloc.h>
#include <iostream>
using namespace std;

int main() {
    void* p = malloc(13);
    cout << "usable size = " << malloc_usable_size(p) << endl;
    free(p);
}

6、怎么看栈的内存空间大小

答案:栈空间大小分几种情况。如果是进程级别看栈上限,在 Linux 下可以直接用 ulimit -s 查看当前 shell 启动进程的栈大小限制。如果想在程序里看,可以用 getrlimit(RLIMIT_STACK, ...)。如果是线程栈,尤其是 pthread 创建的线程,还可以通过线程属性查看和设置线程栈大小。

代码:

#include <sys/resource.h>
#include <iostream>
using namespace std;

int main() {
    rlimit rl;
    getrlimit(RLIMIT_STACK, &rl);
    cout << "stack size: " << rl.rlim_cur << endl;
    return 0;
}

7、C 语言底层内存分配是怎么样的

答案:从进程视角看,内存大致会分成代码段、全局/静态区、堆、栈、共享库映射区这些区域。C 语言里动态分配主要说的是堆区,也就是 malloc/calloc/realloc/free 这一套。

底层上,分配器并不是每次申请一点内存就立刻向内核要一点,它通常会维护自己的堆管理结构。早期常见方式是通过 brk/sbrk 扩展堆顶;大块内存很多实现会直接走 mmap。分配器内部还会维护空闲链表、块头信息、对齐规则、分裂合并逻辑,所以你看到的 malloc 很简单,底下实际做的事情并不少。

如果面试官继续追问,可以顺着说:

  • 小块分配如何避免碎片
  • 大块为什么直接走 mmap
  • calloc 为什么能清零
  • realloc 为什么有时候原地扩,有时候搬迁

8、内存泄漏用什么工具,怎么发现

答案:内存泄漏排查常见工具主要有:

  • valgrind --leak-check=full
  • AddressSanitizer,简称 ASan
  • LeakSanitizer,简称 LSan
  • 一些线上场景会配合 gdbpmapsmemheap profiler

如果是开发阶段,我一般更倾向于 ASan/LSan,因为开销相对更可控,集成也方便;如果是比较细致地查泄漏路径,valgrind 也很直接。

判断有没有泄漏通常不是只看“程序内存大”,而是结合几个现象:

  • 进程 RSS 持续增长且不回落
  • 某类对象创建次数和释放次数不匹配
  • 压测时间越长内存越高
  • 工具直接报未释放块和调用栈

代码:

g++ -fsanitize=address -g test.cpp -o test
./test

或者:

valgrind --leak-check=full ./test

9、类的内存对齐规则

答案:类的内存对齐,本质上和结构体对齐规则一致。通常要记住这几个原则:

  • 每个成员的起始地址要满足该成员对齐要求
  • 整个类的大小要是“最大对齐数”的整数倍
  • 编译器可能会在成员之间插入填充字节
  • 基类子对象、虚函数指针这些也会影响整体布局

比如:

代码:

#include <iostream>
using namespace std;

struct A {
    char c;
    int i;
};

struct B {
    char c1;
    double d;
    char c2;
};

int main() {
    cout << sizeof(A) << endl; // 一般是 8
    cout << sizeof(B) << endl; // 常见是 24
}

Achar 后面通常会补齐到 4 字节边界;B 里因为有 double,整体对齐要求会更高,所以尾部也可能补齐。

10、类可以手动定义对齐数吗,怎

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

03-16 17:06
已编辑
门头沟学院 Java
查看8道真题和解析
点赞 评论 收藏
分享
评论
2
7
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务