【C语言】字符串的本质是什么,为什么 strlen 会读到野内存

很多人用了好一阵子 printf("%s", str) 之后才意识到,C 语言根本没有"字符串类型"。char 数组加一个 \0 才是字符串,缺了那个 \0,你调 strlen 会读到哪里去,没人知道。

这篇把字符串的底层机制说清楚,顺带讲几个能把程序搞崩的坑。


字符串的本质:字符数组 + \0

C 语言的字符串不是一个专门的类型,本质就是一块连续的内存,最后一个字节存着值为 0\0(空字符,null terminator)。

char name[6] = {'h', 'e', 'l', 'l', 'o', '\0'};

等价写法:

char name[6] = "hello";

"hello" 这个字符串字面量本身就含 \0,编译器会把它展开成 6 个字节,不是 5 个。

内存布局:

地址:  1000  1001  1002  1003  1004  1005
内容:   'h'   'e'   'l'   'l'   'o'   '\0'
十六进:  68    65    6C    6C    6F    00

strlenprintf %sstrcpy 这些函数都从起始地址开始往后读,碰到 \0 就停。这就是为什么 \0 是字符串的核心——没有它,这些函数不知道字符串在哪里结束,会一直往后读,进入它不该进的内存区域。


声明方式:三种写法的差异

写法一:字符数组(可修改)

char str[] = "hello";   /* 编译器自动算长度:6 个字节 */
str[0] = 'H';           /* 合法,数组内容可修改 */

str 存的是局部变量,放在栈上,内容可以改。

写法二:指针指向字符串字面量(危险

char *p = "hello";
p[0] = 'H';   /* 运行时崩溃或静默失败 */

"hello" 是字符串字面量,编译器把它放在只读数据段.rodata)。p 指向那块只读内存,写入操作是未定义行为——在 Windows 上通常是访问违规崩溃,在某些平台上可能什么也不发生但结果是错的。

VS2013 编译这行不报错,但运行时会弹出一个访问违规的对话框。正确写法是加 const

const char *p = "hello";   /* 明确告诉自己和编译器:这块内存不能改 */

写法三:固定长度数组

char buf[64];   /* 声明一个 64 字节的缓冲区 */

buf 里的内容是未初始化的垃圾值,没有 \0。直接 printf("%s", buf) 会打印出一堆乱码,直到碰到内存里某个恰好是 0 的字节才停。

初始化方式:

buf[0] = '\0';        /* 方法一:把第一个字节清零,等价于空字符串 */
memset(buf, 0, 64);   /* 方法二:全部清零,更安全 */

常用字符串函数:用法与陷阱

strlen:求长度,不含 \0

#include <string.h>

char s[] = "hello";
size_t len = strlen(s);   /* 返回 5,不是 6 */

性能陷阱strlen 每次调用都从头扫到 \0,时间复杂度 O(n)。把它放在循环条件里是典型的性能问题:

/* 错误写法:每次循环都调一次 strlen,O(n²) */
for (i = 0; i < strlen(s); i++) { ... }

/* 正确写法:先算好长度 */
int len = strlen(s);
for (i = 0; i < len; i++) { ... }

字符串不长的时候看不出差别,字符串一旦上了几千字节,循环里的 strlen 会让程序明显变慢。

strcpy:复制字符串

char dst[10];
strcpy(dst, "hello");   /* 把 "hello\0" 复制到 dst */

strcpy 不检查目标缓冲区够不够大,是经典的缓冲区溢出来源:

char dst[4];
strcpy(dst, "hello");   /* 写了 6 个字节进 4 字节的空间,溢出 */

溢出会覆盖 dst 后面的内存,可能改掉其他变量,可能破坏返回地址,可能什么都不发生——但这正是最危险的情况,因为 bug 不在当场发作,而是在你根本不想到的地方崩溃。

VS2013 的安全版本

/* strcpy_s 会检查目标缓冲区大小,超出时触发错误处理 */
strcpy_s(dst, sizeof(dst), "hello");

strcmp:比较字符串

这里是最常见的初学者错误:用 == 比较两个字符串。

char a[] = "hello";
char b[] = "hello";

if (a == b) { ... }   /* 错!比较的是两个数组的地址,永远不相等 */

if (strcmp(a, b) == 0) { ... }   /* 对!比较的是内容 */

ab 是两个不同的数组,它们的地址不同,a == b 永远是假。strcmp 返回 0 表示两个字符串内容相同,正数/负数表示大小关系(按字典序)。

strcat:拼接字符串

char buf[20] = "hello";
strcat(buf, " world");   /* buf 变成 "hello world" */

同样不检查目标缓冲区大小,同样有溢出风险。安全版本用 strncat(指定最多追加多少字节)或 VS2013 的 strcat_s


完整示例:字符串处理的正确姿势

#include <stdio.h>
#include <string.h>

#define BUF_SIZE 64

int main() {
    char src[] = "Hello, C language";
    char dst[BUF_SIZE];
    int i;
    int len;

    /* 安全复制 */
    strcpy_s(dst, BUF_SIZE, src);
    printf("复制结果:%s\n", dst);

    /* 统计长度,先存起来避免循环里重复调用 */
    len = (int)strlen(dst);
    printf("长度:%d\n", len);

    /* 遍历字符,把小写字母转大写 */
    for (i = 0; i < len; i++) {
        if (dst[i] >= 'a' && dst[i] <= 'z') {
            dst[i] = dst[i] - 'a' + 'A';
        }
    }
    printf("转大写:%s\n", dst);

    /* 字符串比较 */
    if (strcmp(src, dst) != 0) {
        printf("两个字符串内容不同\n");
    }

    return 0;
}

在 VS2013 里按 F5,输出:

复制结果:Hello, C language
长度:17
转大写:HELLO, C LANGUAGE
两个字符串内容不同

避坑清单

坑 1:数组太小,没给 \0 留位置

char s[5] = "hello";   /* 错!"hello" 需要 6 个字节,s 只有 5 个 */

VS2013 会报警告或错误。规则:数组大小至少是字符串长度 + 1。

坑 2:忘记初始化,直接当字符串用

char buf[64];
printf("%s\n", buf);   /* buf 里是垃圾,可能打出一堆乱码 */

声明字符数组后先 memset(buf, 0, sizeof(buf))buf[0] = '\0'

坑 3:用 == 比较字符串内容

已在上面说过,== 比的是地址,不是内容。一律用 strcmp

坑 4:向只读字符串字面量写入

char *p = "hello";
p[0] = 'H';   /* 运行时崩溃 */

需要修改的字符串,用 char [] 而不是 char *

坑 5:strcpy 目标缓冲区不够大

源字符串多长你得自己算,strcpy 不帮你检查。在 VS2013 里优先用 strcpy_s


最佳实践

  • 字符数组大小用 #define 定义常量,方便统一修改,也防止 sizeof 和硬编码不一致

  • 所有字符数组在声明后立即用 memset 清零,而不是依赖局部变量的初始值

  • 在 VS2013 环境下,用 strcpy_sstrcat_ssprintf_s 代替不安全的老版本

  • 循环遍历字符串时先把 strlen 结果存到变量,不要每次循环都调

  • 函数接收字符串参数时,同时传入缓冲区大小,永远不要假设调用方传进来的缓冲区够大


标签#C语言 #字符串 #内存安全 #缓冲区溢出 #VS2013

下载代码方式:https://pan.quark.cn/s/dd3561eca308 在软件开发领域,面向对象编程(OOP)是一种普遍采纳的结构化方法,它使得开发者能够借助模拟现实环境中的实体和关系来构建软件系统。在本案例中,我们观察到的是一个关于借助抽象类来执行不同几何图形面积求解的实践应用。现在,让我们详细分析这一议题。 标题 "应用抽象类计算面积" 清晰地表明我们将要讨论一个抽象类,此类设定了一个用于测量图形面积的标准函数,但并未提供实际的执行过程。抽象类在诸如C#或Java等编程语言中通常借助`abstract`修饰符进行声明,它们无法直接创建对象实例,仅能作为其他类的基础模板。 描述部分提及的"图形界面应用"暗示这是一个基于视觉用户界面(GUI)的系统,可能运用了.NET Framework的Windows Forms或WPF技术,或者是Java平台的Swing或JavaFX框架。在这样环境下,用户能够通过视觉元素与这些几何体进行互动,例如输入相关尺寸并观看到计算得出的面积值。 抽象类“几何体”内嵌了“计算面积”这一抽象函数。在代码层面,这可以被表述为: ```csharp public abstract class GeometricShape { public abstract double CalculateArea(); } ``` 随后,有三个派生类:圆(Circle)、矩形(Rectangle)和三角形(Triangle),它们各自提供了这个抽象函数的具体实现。比如,圆的面积是通过π乘以半径的平方得到的,矩形的面积是长和宽的乘积,而三角形的面积可能是底乘以高再除以2的结果。这些类将提供具体实现来计算它们各自的面积: ```csharp p...
内容概要:本文系统研究了移相控制全桥LLC谐振变换器的工作特性,深入分析其在不同工作模式下的运行机理与性能表现,重点探讨了软开关实现、高效率能量转换及宽范围电压调节等关键技术优势。通过Simulink搭建精确的仿真模型,对谐振腔参数、开关频率、电压增益、系统效率等关键指标进行仿真分析,验证了理论设计的正确性。同时,详细研究了移相控制策略对系统动态响应、稳定性和轻载/重载工况适应性的影响,揭示了控制参数与电路参数之间的耦合关系,为高频高效电源设计提供了理论依据和实践指导。; 适合人群:具备电力电子技术、模拟电路及自动控制理论基础,从事开关电源、新能源变换器、电动汽车充电模块或高频电源系统研发的工程师及高校研究生。; 使用场景及目标:①掌握全桥LLC谐振变换器的拓扑结构、工作原理与关键参数设计方法;②理解移相控制在实现零电压开通(ZVS)和零电流关断(ZCS)中的作用机制;③通过Simulink仿真掌握变换器建模、参数优化与性能评估流程,服务于实际产品开发与学术课题研究。; 阅读建议:建议读者结合提供的Simulink仿真模型进行同步操作,重点关注谐振网络(Lr, Lm, Cr)参数与移相角之间的匹配设计,深入理解软开关条件的形成过程,并通过调整负载和输入电压进行多工况仿真,以全面掌握系统动态特性。
一、项目概述 本项目设计并实现了一个基于STM32F103C8T6微控制器的温湿度监测与报警系统。系统通过DHT11传感器实时采集环境温湿度数据,当数据超过预设阈值时,蜂鸣器发出声音报警,同时通过4位数码管实时显示当前温湿度值。整个系统采用Type-C接口供电,支持USB串口通信,便于数据调试与传输。 二、硬件组成 1. 主控模块:STM32F103C8T6最小系统(含晶振、复位、BOOT电路、去耦电容)。 2. 传感器模块:DHT11温湿度传感器(单总线通信)。 3. 报警模块:有源蜂鸣器(三极管驱动)。 4. 显示模块:4位共阴数码管(TM1637驱动)。 5. 通信模块:CH340N USB转串口芯片(Type-C接口)。 6. 电源模块:AMS1117-3.3稳压电路(5V转3.3V)。 7. 调试接口:SWD调试排针(SWDIO/SWCLK)。 三、开发流程 1. 原理图设计:使用嘉立创EDA绘制完整原理图,包含各功能模块。 2. PCB设计:进行PCB布局布线,注重电源完整性、信号隔离及晶振走线。 3. 制板焊接:嘉立创打样,手工焊接元器件。 4. 软件开发:STM32CubeMX配置工程,编写DHT11驱动、TM1637显示、串口通信及报警逻辑。 5. 调试测试:通过SWD下载程序,串口输出数据,测试温湿度采集与报警功能。 四、项目特点 - 完整的嵌入式系统开发流程(硬件设计→制板→软件编程→调试)。 - 多传感器数据融合与实时显示。 - 低功耗设计,支持USB供电与串口通信。 - 模块化设计,便于功能扩展(如添加WiFi模块、数据存储等)。 五、应用前景 适用于家庭环境监测、农业大棚、仓库温湿度监控等场景
内容概要:本文提出了一种基于TOGI-SOGI混合积分器的光储并网谐波自适应抑制方法,并通过Simulink实现完整仿真验证。该方法融合三重二阶广义积分器(TOGI)与标准二阶广义积分器(SOGI),能够精确提取电网电压中的基波正序分量,有效分离并抑制高次谐波成分,尤其在电网电压畸变条件下显著提升了并网逆变器的控制精度与电能质量。文中详细阐述了混合积分器的结构设计、谐波检测机制、自适应调节策略及其在锁相环(PLL)中的集成应用,并通过构建光储并网系统模型进行多工况仿真,结果表明该方法具备优良的动态响应特性、强鲁棒性及谐波抑制能力。; 适合人群:具备电力电子、新能源发电、自动控制等相关专业背景,熟悉Simulink仿真环境,从事光伏并网控制、电能质量治理、微电网运行与控制等领域研究的科研人员、工程技术人员及研究生。; 使用场景及目标:①应用于电网存在谐波污染的光伏发电并网场景,提升逆变器在非理想电网下的运行稳定性;②为基于广义积分器的先进锁相技术与谐波补偿策略提供理论支持与实现范例;③服务于高校科研项目复现、学位论文研究、电力电子控制器原型开发及学术成果验证。; 阅读建议:建议学习者结合提供的Simulink模型深入剖析TOGI-SOGI的内部信号流向与参数整定逻辑,重点关注谐波分量的解耦过程与自适应控制模块的实现机制,可通过设置不同谐波含量、频率偏移等扰动工况进行对比测试,以全面掌握其在复杂电网环境下的适应性与优越性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无相求码

你的支持,是我原创的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值