网易互娱 C++开发实习 一面(暑期)
1. 内存对齐怎么理解?下面两个结构体的大小是多少?
答案:内存对齐主要是为了让 CPU 更高效地访问数据。结构体成员不是简单地按字段大小相加,而是会按照成员自身对齐要求和结构体整体对齐要求插入 padding。
struct S1 {
char c;
double b;
int i;
};
struct S2 {
S1 s;
float f;
};
在常见 64 位环境下,double 按 8 字节对齐,int 按 4 字节对齐,char 按 1 字节对齐。S1 的布局一般是:
char c占 1 字节- 为了让
double b按 8 字节对齐,中间补 7 字节 double b占 8 字节int i占 4 字节- 结构体整体要按最大对齐值 8 对齐,末尾再补 4 字节
所以 sizeof(S1) 通常是 24。S2 中 S1 占 24 字节,float f 占 4 字节,结构体整体仍然要按 8 字节对齐,所以末尾补 4 字节,sizeof(S2) 通常是 32。
代码:
#include <iostream>
using namespace std;
struct S1 {
char c;
double b;
int i;
};
struct S2 {
S1 s;
float f;
};
int main() {
cout << sizeof(S1) << endl;
cout << sizeof(S2) << endl;
return 0;
}
2. 如何减少结构体 S1 的大小?除了改变变量顺序还有什么办法?
答案:减少结构体大小最直接的办法是调整成员顺序,让大对齐成员放前面,小对齐成员放后面,尽量减少中间 padding。
比如把 double 放前面:
struct S1 {
double b;
int i;
char c;
};
这样常见 64 位环境下大小会从 24 降到 16。除了调整顺序,还可以用 #pragma pack 或 alignas 控制对齐,但这类方式要慎重。强行压缩对齐可能会导致非对齐访问,某些平台上性能下降,甚至出现访问异常。如果是网络协议或磁盘文件格式,更推荐明确序列化字段,而不是直接把结构体内存写出去。
代码:
#include <iostream>
using namespace std;
struct S1_old {
char c;
double b;
int i;
};
struct S1_new {
double b;
int i;
char c;
};
int main() {
cout << sizeof(S1_old) << endl;
cout << sizeof(S1_new) << endl;
return 0;
}
3. 左值和右值有什么区别?
答案:左值通常表示有稳定身份、可以取地址的对象,比如变量、数组元素、解引用后的指针。右值通常表示临时值,表达式结束后就会消亡,比如字面量、临时对象、函数返回的非引用对象。简单说,左值更强调“对象在哪里”,右值更强调“这个值是什么”。C++11 引入右值引用之后,右值的价值变得更明显,因为它可以用于移动语义,避免不必要的深拷贝。
代码:
#include <iostream>
using namespace std;
int getValue() {
return 10;
}
int main() {
int a = 1; // a 是左值
int b = getValue(); // getValue() 返回值是右值
int* p = &a; // 可以取地址
// int* q = &getValue(); // 错误,临时右值不能这样取地址
cout << *p << " " << b << endl;
}
4. 右值引用的作用是什么?
答案:右值引用主要服务于移动语义和完美转发。没有右值引用时,很多临时对象只能被拷贝;有了右值引用后,可以把临时对象内部资源直接转移给新对象,减少内存分配和数据复制。比如一个对象内部维护堆内存,拷贝时要重新分配并复制内容,而移动时只需要转移指针,再把原对象置为空即可。所以右值引用不是为了让语法更复杂,而是为了解决资源转移效率问题。
代码:
#include <iostream>
using namespace std;
class Buffer {
public:
Buffer(size_t n) : data_(new int[n]), size_(n) {}
~Buffer() {
delete[] data_;
}
Buffer(Buffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
private:
int* data_;
size_t size_;
};
5. std::move 的底层原理是什么?
答案:std::move 本身并不会移动任何东西,它只是一个强制类型转换,把表达式转换成右值引用。真正发生资源转移的是后续调用的移动构造函数或移动赋值函数。所以如果一个类没有实现移动构造,或者移动构造不可用,写了 std::move 也不一定能减少拷贝。另外,被 std::move 之后的对象仍然是有效对象,但它的具体内容通常不应该再依赖,只适合析构、重新赋值或者保持可析构状态。
代码:
#include <iostream>
#include <utility>
using namespace std;
class A {
public:
A() = default;
A(const A&) {
cout << "copy\n";
}
A(A&&) noexcept {
cout << "move\n";
}
};
int main() {
A a;
A b = std::move(a);
return 0;
}
6. 智能指针是什么?为什么要使用智能指针?
答案:智能指针是用对象来管理指针资源的一种 RAII 封装。它的核心作用是把资源释放放到析构函数里,让对象生命周期结束时自动释放资源,减少内存泄漏、重复释放和异常路径忘记释放的问题。常见智能指针有 unique_ptr、shared_ptr 和 weak_ptr。unique_ptr 表示独占所有权,shared_ptr 表示共享所有权,weak_ptr 用来观察对象并解决循环引用。
代码:
#include <iostream>
#include <memory>
using namespace std;
struct Node {
~Node() {
cout << "~Node\n";
}
};
int main() {
unique_ptr<Node> p1 = make_unique<Node>();
auto p2 = make_shared<Node>();
weak_ptr<Node> wp = p2;
if (auto sp = wp.lock()) {
cout
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
