You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言char指针(字符串/数组)内存分配及strcpy异常问题

K&R char指针与内存分配相关运行现象底层解释

所有"运行没报错"的现象本质都是未定义行为(UB):C语言不会在语言层面对野指针、内存越界做强制运行时检查,只要内存操作没踩到操作系统标记为受保护的内存页、没破坏栈/堆的关键元数据,程序就可能"看起来正常运行",绝不代表代码写法是合法的。


前置核心概念

  • 仅定义char *p只会创建一个存储内存地址的指针变量,本身不会分配存储字符串内容的空间;未初始化的局部指针变量,初始值是栈上残留的随机垃圾值。
  • 双引号包裹的字符串字面量(比如"HI"、"randomstring")会被编译到程序的只读数据段(.rodata),操作系统会给这块内存标记只读属性,任何往该地址执行写操作的行为都会直接触发段错误。
  • strcpy(dst, src)的执行逻辑是从src指向的地址开始逐字节拷贝,直到遇到值为\0的字节才停止,函数本身完全不会检查dst指向的内存是否可写、是否有足够存储空间,所有合法性约束全部由编码者自行保证。
  • 栈帧内存是连续布局的,局部变量、函数返回地址、栈寄存器状态都存在栈空间中;未初始化指针的随机值如果刚好落在当前进程可写的栈/堆范围内,写操作不会立刻触发内存保护,但会偷偷篡改其他无关数据。

各示例运行现象底层原因

示例1:未初始化指针调用strcpy不报错

// No errors
char *name;
char *altname;
strcpy(altname,name);

两个指针都是未初始化的局部变量,存储的是栈上残留的随机地址。不报错纯粹是概率事件:要么两个随机值刚好落在当前进程可写的内存区间内,拷贝操作没触发内存保护;要么低优化等级下,未初始化指针刚好指向栈上一块暂时未被占用的预留空间,写入少量字节不会立刻引发异常。
这是100%的未定义行为,换编译选项、换系统版本、甚至新增几行无关代码,都可能立刻崩溃或输出乱码。

示例2:altname未初始化却"拷贝成功"

// No errors, internals of *name have been successfully moved to *altname
char *name = "HI";
char *altname;
strcpy(altname, name);

和示例1本质相同,属于野指针的巧合:name指向只读段存储的"HI\0",总长度仅3字节;altname的随机初始值刚好指向一块可写、且暂时未被其他有效数据占用的内存,往里面写入'H'、'I'、'\0'三个字节没有触发保护,后续读取altname时刚好能拿到这三个字节,就呈现出"拷贝成功"的假象。
实际上这块内存不属于当前变量所有,后续其他变量分配到该地址时内容会被直接覆盖,随时可能出现逻辑错误。

示例3、4触发段错误的原因

// 示例3 段错误
char *name = "HI";
char *altname = "randomstring";
strcpy(altname, name);

// 示例4 段错误
char *name;
char *altname = "     ";
strcpy(altname, name);

两个示例触发崩溃的核心原因完全一致:altname被显式赋值为字符串字面量的地址,字符串字面量存储在只读数据段,操作系统对该内存页加了只读保护;strcpy会向目标地址执行写操作,往只读内存写数据会被CPU内存保护单元直接拦截,操作系统会向进程发送SIGSEGV信号,触发段错误崩溃。
示例4额外存在一个问题:name是未初始化的野指针,就算altname指向可写内存,从name存储的随机地址读数据本身也可能触发段错误,只是这里向只读区写的操作先触发了崩溃。

示例5:数组s长度大于8才能正常拷贝的现象

// copies internals of *name only if size of char s[] > 8???
char s[9];
char n[] = {'c', 'b'};
char *name = n;
char *altname = s;
strcpy(altname, name);

这个现象的核心问题是定义的n数组缺少字符串结束符\0:

  • 写法char n[] = {'c','b'}只会分配2字节空间,存储'c'和'b'两个字符,末尾不会自动补\0。
  • strcpy会持续拷贝直到遇到\0才停止,也就是说拷完'b'之后不会停下,会继续读取n数组后方栈上的残留垃圾数据,一直向后查找值为0的字节。
  • 受栈对齐规则、局部变量布局影响,n数组后方通常要经过6~8字节的栈填充、残留数据区才会碰到0值。当s长度小于等于8时,strcpy写入的字节数会超出s的容量,覆盖栈上s后方的关键数据(比如旧栈基址、函数返回地址、栈保护哨兵值),要么导致逻辑错乱,要么直接触发栈保护机制崩溃。
  • 当s长度大于等于9时,刚好能装下从n地址开始到第一个0值之间的所有字节,就呈现出"正常拷贝"的假象,本质依然是巧合——只要栈上的垃圾数据分布变化(比如新增几个局部变量、调整编译优化等级),拷贝长度随时可能增加,再次出现越界。
    正确写法是给n数组手动补结束符:char n[] = {'c','b','\0'};,或者直接用字符串字面量初始化char n[] = "cb";让编译器自动补\0,此时只要s长度大于等于3就能稳定正常拷贝。

常见误区澄清

  • 不存在"没报错就是写对了"的情况,C语言未定义行为的表现完全随机,正常运行、崩溃、输出乱码都是符合语言标准的可能结果。
  • 定义char指针从来不会自动分配存储字符串的空间,要么让指针指向提前声明的char数组,要么用malloc动态申请对应长度的内存,用完后调用free释放。
  • 所有字符串字面量都是只读的,永远不要把strcpy、strcat这类会执行写操作的函数的目标指针指向字符串字面量。

内容的提问来源于stack exchange,提问作者かわいそうu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:42:18