c语言安全分析(一)——字符串(1)

文章目录


介绍

嗨,我是艾丽卡,很高兴和你聊聊C语言中的字符串和数组。想象一下,字符串就像一列小火车,每个车厢代表一个字符,而最后一个特别的车厢------空字符 '\0'------告诉我们火车到站了,也就是字符串的结束。

  1. 界限(Bound)

    • 这就像数火车车厢的数量,包括那个告诉我们火车结束的特别的车厢。
  2. 低位地址(Lo)

    • 这是火车队列的第一个车厢的地址,也就是字符串的第一个字符。
  3. 高位地址(Hi)

    • 这是最后一个车厢的地址,也就是空字符的地址。
  4. TooFar

    • 这就像是火车队列结束后,我们再往前多走一步的位置。虽然我们可以指向那里,但是如果真的去那里,就会超出火车的范围,导致未定义行为。
  5. 目标大小(Tsize)

    • 这就像测量整个火车队列占用的轨道长度,也就是整个字符串包括空字符在内的总字节数。
  6. 空字符结尾(Null-terminated)

    • 这表示我们的火车队列总是以那个特别的车厢结束,这样我们就知道火车什么时候到站。
  7. 长度(Length)

    • 这是计算火车车厢数量的方法,但不包括那个特别的结束车厢。

处理字符串时,我们得像列车长一样小心翼翼,确保不会让火车出轨,也就是说,要确保我们的代码不会越界,不会溢出缓冲区,这样才能保证我们的程序既安全又稳定。


一、\0 NULL false

在C语言中,sizeof 运算符用于获取一个变量或类型在内存中所占的字节数。对于数组,sizeof(array) 会返回整个数组所占的字节数,包括数组中的所有元素。

当你有一个数组时,比如 char str[] = "hello";sizeof(str) 会返回字符串 "hello" 所占的字节数,包括最后的空字符(null terminator)\0。这是因为在C语言中,字符串是以空字符结尾的字符数组。

C标准确实允许创建指向数组末尾元素之后加1位置的指针。这意味着你可以有一个指针指向数组的最后一个元素之后的位置,但这个位置实际上是数组的"边界之外"。例如:

c 复制代码
char str[] = "hello";
char *ptr = str + sizeof(str) / sizeof(str[0]);

在这个例子中,ptr 指向的是数组 str 的末尾元素之后的位置,也就是数组的"边界之外"。这个指针是合法的,因为它没有越界,它只是指向了数组末尾元素之后的一个位置。但是,你不能对这个位置进行解引用操作,因为这样做会产生未定义行为,即程序可能会崩溃或者产生不可预测的结果。

在处理字符串时,这个特性可以用来遍历字符串直到遇到空字符。例如,下面的代码可以用来计算字符串的长度:

c 复制代码
char str[] = "hello";
char *ptr = str;
while (*ptr) {
    ptr++;
}
int length = ptr - str;

在这个例子中,ptr 会遍历字符串直到遇到空字符,然后计算出字符串的长度。但是,一旦 ptr 指向了空字符,就不应该再进行解引用操作,因为那会导致未定义行为

c 复制代码
#include <stdio.h>
int main() {
    char str[] = "hello"; // 包含空字符的字符串
    char *ptr = str;     // 指向字符串的开始

    // 遍历字符串直到遇到空字符
    while (*ptr) {
        ptr++; // 移动指针但不解引用TooFar位置
    }

    // 此时ptr指向TooFar位置,即空字符之后的位置
    // 我们不应该解引用ptr,因为它指向的是未定义的内存区域

    // 计算字符串的长度(不包括空字符)
    int length = ptr - str;
    printf("The length of the string is: %d\n", length);

    return 0;
}
bush 复制代码
The length of the string is: 5

好奇的是为什么程序while会停止

我们尝试传入\0

c 复制代码
#include <stdio.h>

int main() {
    char str[] = "hello"; // 包含空字符的字符串
    char *ptr = str;     // 指向字符串的开始

    // 遍历字符串直到遇到空字符
    // while (*ptr) {
    //     ptr++; // 移动指针但不解引用TooFar位置
    // }
    while ('\0') {
        ptr++; // 移动指针但不解引用TooFar位置
    }
    // 此时ptr指向TooFar位置,即空字符之后的位置
    // 我们不应该解引用ptr,因为它指向的是未定义的内存区域

    // 计算字符串的长度(不包括空字符)
    int length = ptr - str;
    printf("The length of the string is: %d\n", length);

    return 0;
}
bush 复制代码
he length of the string is: 0

可以看出\0可以终止while,而false也可以。

c 复制代码
while (false) {
        ptr++; // 移动指针但不解引用TooFar位置
    }
#
  1. \0(空字符)

    • \0 是一个字符常量,代表ASCII码中的空字符,其整数值是0。在C语言中,字符串以空字符 \0 结尾,这是字符串结束的标志。当 \0 用作条件表达式时,它的值是0,因此在布尔上下文中被视为 false
  2. false

    • false 是C语言中的一个布尔值,代表逻辑假。在C99及以后的标准中,bool 类型被引入,其中 false 是预定义的布尔值,其值为0。

尽管 \0false 在整数值上都是0,并且在布尔上下文中都被视为 false,但它们在语义上是不同的。\0 用于表示字符串的结束,而 false 用于表示布尔逻辑中的假值。

在C语言中,NULL 是一个宏,它被定义为一个空指针常量。它的值通常是 (void *)0,即一个指向 void 类型的指针,其值为0。NULL 用于表示一个空(或无效)的指针。

NULL 被用在 while 循环的条件中时,它的值实际上是一个指针,而不是一个整数。在C语言中,任何非零值在布尔上下文中都被视为 true,而零值(包括 NULL 指针)被视为 false。因此,while (NULL) 这个条件会被评估为 false,循环体不会被执行。

这里是一个例子:

c 复制代码
#include <stdio.h>

int main() {
    while (NULL) {
        printf("This will not be printed.\n");
    }
    return 0;
}

在这个程序中,while 循环的条件是 NULL,因此条件评估为 false,循环内部的 printf 语句不会被执行。

总结来说,NULL 在布尔上下文中被视为 false,因为它的值是零。这与 \0(空字符)和 false(布尔假值)在布尔上下文中的表现是一致的,尽管它们在类型和用途上有所不同。

二,Toofar

在C语言中,越界解引用是指访问数组或字符串超出其有效范围的内存位置。这通常是不安全的,因为它可能导致未定义行为,包括程序崩溃、数据损坏或安全漏洞。下面是一个越界解引用的例子:

c 复制代码
#include <stdio.h>

int main() {
    char str[] = "hello"; // 字符串字面值,包含空字符'\0'

    // 故意越界解引用
    printf("%c\n", str[sizeof(str) / sizeof(str[0])]); // 这将打印空字符'\0'

    // 越界解引用
    printf("%c\n", str[sizeof(str) / sizeof(str[0]) + 1]); // 这将尝试访问空字符之后的内存

    return 0;
}
bush 复制代码
0
□

在这个例子中,str 是一个字符数组,它包含了字符串 "hello" 和一个空字符 \0sizeof(str) / sizeof(str[0]) 计算得到的是数组中的元素数量,不包括空字符。因此,str[sizeof(str) / sizeof(str[0])] 是访问数组中最后一个元素(即空字符)的有效操作。

然而,str[sizeof(str) / sizeof(str[0]) + 1] 尝试访问空字符之后的内存位置,这是一个越界解引用。这个操作是未定义的,因为它访问了数组分配的内存之外的区域。在某些系统和编译器上,这可能导致程序崩溃或不可预测的行为。

越界解引用是编程中应该避免的错误,因为它可能导致严重的安全问题。正确的做法是总是确保在访问数组或字符串时不会超出其界限。在实际编程中,应该使用循环或其他控制结构来确保不会越界,或者使用安全的字符串处理函数,如 strncpysnprintf 等,它们可以帮助防止缓冲区溢出和越界错误。


相关推荐
星河梦瑾7 分钟前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黑客Ela26 分钟前
对安全的认知
安全
东风吹柳34 分钟前
观察者模式(sigslot in C++)
c++·观察者模式·信号槽·sigslot
嵌入式科普40 分钟前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A43 分钟前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
大胆飞猪2 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
1 9 J2 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
夕泠爱吃糖2 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
长潇若雪2 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象