代码示例:1.2.6 字符串与安全函数
全部示例均标注编译命令、适用环境与实测状态;完整验证记录见《03.5-代码编译运行验证记录》。
examples/ex1-string-ops.c · 常用字符串函数(strlen/sizeof/strcmp/strcpy/strcat/strstr)
/* ex1-string-ops.c —— 常用字符串函数基本用法(1.2.6) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex1-string-ops.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic * 运行:./ex1(Windows: ex1.exe) */ #include <stdio.h> #include <string.h> int main(void) { char s1[] = "hello"; char s2[16] = "world"; printf("strlen(s1) = %zu(不含结尾 \\0)\n", strlen(s1)); printf("sizeof(s1) = %zu(数组总字节数,含 \\0)\n", sizeof(s1)); printf("strcmp(s1, s2) = %d(负值:s1 按字典序小于 s2)\n", strcmp(s1, s2)); char buf[32]; strcpy(buf, s1); strcat(buf, ", "); strcat(buf, s2); printf("拼接后: %s(长度 %zu)\n", buf, strlen(buf)); printf("strstr(\"%s\", \"wor\") -> %s\n", buf, strstr(buf, "wor")); return 0; }examples/ex2-my-strlen-strcpy.c · 手写 my_strlen / my_strcpy(\0 处理与断言验证)
/* ex2-my-strlen-strcpy.c —— 手写 strlen/strcpy 并用断言与标准库对照(1.2.6) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex2-my-strlen-strcpy.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic * 运行:./ex2(Windows: ex2.exe) */ #include <stdio.h> #include <string.h> #include <assert.h> /* 手写 strlen:数到结尾 \0 为止 */ static size_t my_strlen(const char *s) { size_t n = 0; while (s[n] != '\0') { n++; } return n; } /* 手写 strcpy:必须连同结尾 \0 一起复制,否则目标不是"字符串" */ static char *my_strcpy(char *dst, const char *src) { size_t i = 0; do { dst[i] = src[i]; } while (src[i++] != '\0'); return dst; } int main(void) { const char *text = "embedded linux"; char buf[32]; assert(my_strlen(text) == strlen(text)); printf("my_strlen(\"%s\") = %zu(与标准库一致,断言通过)\n", text, my_strlen(text)); my_strcpy(buf, text); assert(strcmp(buf, text) == 0); printf("my_strcpy 后 buf = \"%s\"(含结尾 \\0,strcmp 断言通过)\n", buf); return 0; }examples/ex3-fgets-safe.c · fgets 读行 + 去除换行符 + snprintf 拼接(安全替代)
/* ex3-fgets-safe.c —— fgets 读行 + 去除换行符 + snprintf 拼接(1.2.6) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex3-fgets-safe.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic * 运行:./ex3(Windows: ex3.exe);两行输入来自键盘或重定向 * 说明:只用 fgets(受 sizeof 限制,不会越界写);不演示 gets(C11 已移除,危险机理见 ex4 概念讲解) */ #include <stdio.h> #include <string.h> /* fgets 会保留行尾换行符:去掉 \n(Windows 同时去掉 \r\n) */ static void trim_newline(char *s) { size_t n = strlen(s); if (n > 0 && s[n - 1] == '\n') { s[--n] = '\0'; } if (n > 0 && s[n - 1] == '\r') { s[--n] = '\0'; } } int main(void) { char name[16]; char line[64]; char out[96]; printf("请输入名字(最多 15 字符): "); if (fgets(name, sizeof(name), stdin) == NULL) { printf("读取失败或输入结束\n"); return 1; } trim_newline(name); printf("请输入一句话: "); if (fgets(line, sizeof(line), stdin) == NULL) { printf("读取失败或输入结束\n"); return 1; } trim_newline(line); /* snprintf 限制目标大小;返回值是"本应写入"的字符数,可用于截断判断 */ int written = snprintf(out, sizeof(out), "%s 说:%s(本行内容长 %d)", name, line, (int)strlen(line)); printf("%s\n", out); printf("snprintf 返回值 = %d(本应写入的字符数;>= %zu 即发生截断)\n", written, sizeof(out)); return 0; }examples/ex4-gets-danger.txt · gets() 危险机理静态讲解(conceptual:不执行危险输入)
# 1.2.6 ex4(conceptual 讲解文本,不编译不运行):gets() 的危险机理 # # 危险写法(只讲解,绝不执行): # char buf[8]; # gets(buf); // C11 已移除该函数 # # 1. gets 只接受缓冲区指针这一个参数,无法指定最大长度; # 2. 输入超过 7 个字符(留 1 字节给结尾 \0)时,gets 仍继续向 buf 之后的内存写入, # 覆盖栈上的其他局部变量乃至返回地址——这就是缓冲区溢出(buffer overflow); # 3. gets 是历史上最著名的安全漏洞根源之一(1988 年 Morris 蠕虫利用的正是它); # 4. C11 标准已删除 gets(C99 起标注废弃);用 -std=c11 编译含 gets 的代码,现代编译器 # 通常报 error 或强制 warning,某些遗留库仍保留实现——一律不要使用; # 5. 正确替代:fgets(buf, sizeof(buf), stdin)——写入永远受 sizeof(buf) 限制; # fgets 会保留行尾 \n,需要时按 ex3 的 trim_newline 去掉。 # # 验证依据(documentation_checked,未执行任何危险输入): # - C11 标准 7.21.7.7(gets 已移除)与 7.21.7.2(fgets 的 n 参数限制); # - 缓冲区溢出原理:无长度检查的写入越过数组边界,破坏相邻内存。 # 纪律:不通过执行危险代码演示未定义行为——本示例为静态讲解。