Appearance
C 字符串 / 内存函数辨析:memcpy 底层、strcpy 缺陷、memcmp 判结构体
对应小林 C++「C++基础/内存函数」题组:
memcpy底层原理、strcpy有何缺陷、sizeof vs strlen、如何判结构体相等(是否能用 memcmp)、以及 memmove/安全版本。虽属 C,但 C++ 面试同样爱考(很多八股冷门出自这里)。
一、memcpy 的底层原理(小林的“底层原理”题要答到字节/拷贝层级)
处理 n 字节从 src 拷到 dst,标准只要求:不重叠(重叠属 UB),未规定算法。现代实现(glibc/libc++ 的 __builtin 转机器)逐级优化:
- 先处理非对齐的头几字节(按一个字大小对齐 dst/src)。
- 主路径用宽字宽 / 向量化拷贝:如一次拷 8/16/32/64 字节(
movupsSSE_mm_loadu_si128),减少指令条数 → 受内存带宽上限主导,不等于“一条条字节”。 - 尾部落回按字节拷齐剩余。
手写面试版(体现“按块提升吞吐”的思想即可,不必真模拟向量化):
cpp
void* my_memcpy(void* dst, const void* src, size_t n){
unsigned char* d = (unsigned char*)dst;
const unsigned char* s = (const unsigned char*)src;
// 生产前提:dst/src 不允许重叠。此处朴素逐字节即可回答语义;
// 讲优化再说按 size_t/宽字多字节搬
for (size_t i=0;i<n;i++) d[i]=s[i];
return dst;
}要点:不能重叠;常用 restrict(restrict)告诉编译器两指针不 alias → 才能向量化/宽读。若可能重叠必须 memmove。
二、memcpy vs memmove(“为什么有俩”必答)
memcpy:语义为不处理重叠(重叠=UB)。memmove:保证 overlap 也安全——从重叠方向判断决定正拷/反拷(若 dst>src 且区域重叠就反向从尾开始拷,避免源被覆盖)。
cpp
// 自实现关键:方向检测
char tmp? 不好(要额外 buf)。常见:if (d < s) 正向; else if (d > s) 反向(从 n 倒着拷);(不必申请临时:直接判断源目标相对位置,用正序或倒序规避覆盖即可,overlap 同向退化即普通拷。)
三、strcpy 的缺陷(小林原题——要答出“为什么不安全”)
strcpy(dst, src):把 src 拷到 '\0' 到 dst,不检查 dst 容量 → 源长于目标缓冲区就越界写(经典 stack buffer overflow 漏洞源)。
缺陷/危险点:
- 无边界检查:调用者须保证 dst 足够,否则栈/堆越界(CVE 高发)。
- 目标必须以足够大 buffer 预留;无返回可做长度反馈意义较弱。
- 中文/多字节字符串按字节不会自动适配(凭 '\0' 截止,无长度体系)。
改进/安全替代:
| 函数 | 说明 |
|---|---|
strncpy(dst,src,n) | 限制最大 n 拷;但 src≥n 时不补 '\0',需自己补——仍易用错 |
自家 snprintf(dst,size,"%s",src) | 按 size 上限并总是 NUL 结尾(推荐,安全) |
| C++ 项目 | 用 std::string / += 彻底避免 C 字符串拷贝手工管理 |
C23 strcpy_s/strncpy_s(可选) | 边界感知版本,依赖实现 |
面试答法:“strcpy 不做目标容量校验,越界写是它的根本缺陷;工程中改用 std::string 或 snprintf,std 字符串是零成本且类型安全。”
相邻:sizeof vs strlen(数组形参退化、见 macro-cast §五)
strlen 数到 '\0' 为止(不含终止符);sizeof 编译期整块字节。传数组给形参退化为指针→sizeof(p)=8——正是漏拷贝一排 bug 的来源。
四、如何判断结构体相等?能用 memcmp 吗?(高频陷阱题)
不能直接 memcmp(&a,&b,sizeof(a))==0 判断结构体“逻辑相等”,原因:
- Padding 字节未初始化:结构体成员间/尾部对齐填充字节内容未定义 → 两个“值相同”的结构体 padding 不同,memcmp 可能返回非 0(假不等)。
- 指针成员:比的是地址不是指向内容(浅比较)。
- 浮点成员:
-0.0 == +0.0为真但比特不同;NaN 永不等于自身。memcmp 按位比会给出与==语义相悖结果。 - 含
std::string/动态容器:内部指针/堆地址不同但逻辑“同值”。 - 悬垂/未初始化成员区域。
正确做法:
- 逐成员用
==比较(值语义要写operator==); - 或先
memset(&a,0,sizeof a)/整体= {}清零 padding 后,仅对 PD(plain,无指针/浮点/动态成员、成员也都是可位比较)结构才可能安全 memcmp(工程仍更推荐写显式 operator== 更可控)。
一句话:memcmp 比你想象的“更底层”——它按内存字节比,无法表达 C++ 的值相等语义;给它喂 padding 或外部资源字段就会出错。
五、安全字符串处理口诀
- 能
std::string就别 C 字符串。 - 必须 C 接口时:定容 +
snprintf型安全写;读侧用strnlen防超读。 - 接收外部输入一律长度受限,防缓冲区溢出。
六、自测
- memcpy 与 memmove 语义差别 & 何时必然用 move?—— 可能 overlap 时。
- 为什么 strcpy 被评为不安全而 memcpy 的重点不同?—— strcpy 目标容量缺检查;memcpy 文档明言不得重叠(由调用方负责)。
- 结构体判等用 memcmp 的 4 个坑列得出吗?