跳到主要内容
🔍

代码示例: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 参数限制);
    # - 缓冲区溢出原理:无长度检查的写入越过数组边界,破坏相邻内存。
    # 纪律:不通过执行危险代码演示未定义行为——本示例为静态讲解。