Skip to content

C 字符串 / 内存函数辨析:memcpy 底层、strcpy 缺陷、memcmp 判结构体

对应小林 C++「C++基础/内存函数」题组:memcpy 底层原理、strcpy 有何缺陷、sizeof vs strlen、如何判结构体相等(是否能用 memcmp)、以及 memmove/安全版本。虽属 C,但 C++ 面试同样爱考(很多八股冷门出自这里)。

一、memcpy 的底层原理(小林的“底层原理”题要答到字节/拷贝层级)

处理 n 字节从 src 拷到 dst,标准只要求:不重叠(重叠属 UB),未规定算法。现代实现(glibc/libc++ 的 __builtin 转机器)逐级优化:

  1. 先处理非对齐的头几字节(按一个字大小对齐 dst/src)。
  2. 主路径用宽字宽 / 向量化拷贝:如一次拷 8/16/32/64 字节(movups SSE _mm_loadu_si128),减少指令条数 → 受内存带宽上限主导,不等于“一条条字节”。
  3. 尾部落回按字节拷齐剩余。

手写面试版(体现“按块提升吞吐”的思想即可,不必真模拟向量化):

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;
}

要点:不能重叠;常用 restrictrestrict)告诉编译器两指针不 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 漏洞源)。

缺陷/危险点:

  1. 无边界检查:调用者须保证 dst 足够,否则栈/堆越界(CVE 高发)。
  2. 目标必须以足够大 buffer 预留;无返回可做长度反馈意义较弱。
  3. 中文/多字节字符串按字节不会自动适配(凭 '\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 判断结构体“逻辑相等”,原因:

  1. Padding 字节未初始化:结构体成员间/尾部对齐填充字节内容未定义 → 两个“值相同”的结构体 padding 不同,memcmp 可能返回非 0(假不等)。
  2. 指针成员:比的是地址不是指向内容(浅比较)。
  3. 浮点成员-0.0 == +0.0 为真但比特不同;NaN 永不等于自身。memcmp 按位比会给出与 == 语义相悖结果。
  4. std::string/动态容器:内部指针/堆地址不同但逻辑“同值”。
  5. 悬垂/未初始化成员区域。

正确做法

  • 逐成员用 == 比较(值语义要写 operator==);
  • 或先 memset(&a,0,sizeof a)/整体 = {} 清零 padding 后,仅对 PD(plain,无指针/浮点/动态成员、成员也都是可位比较)结构才可能安全 memcmp(工程仍更推荐写显式 operator== 更可控)。

一句话:memcmp 比你想象的“更底层”——它按内存字节比,无法表达 C++ 的值相等语义;给它喂 padding 或外部资源字段就会出错。

五、安全字符串处理口诀

  • std::string 就别 C 字符串。
  • 必须 C 接口时:定容 + snprintf 型安全写;读侧用 strnlen 防超读。
  • 接收外部输入一律长度受限,防缓冲区溢出。

六、自测

  1. memcpy 与 memmove 语义差别 & 何时必然用 move?—— 可能 overlap 时。
  2. 为什么 strcpy 被评为不安全而 memcpy 的重点不同?—— strcpy 目标容量缺检查;memcpy 文档明言不得重叠(由调用方负责)。
  3. 结构体判等用 memcmp 的 4 个坑列得出吗?

C++ 面试八股 · VitePress 版