当strnlen()使用的最大长度大于实际的缓冲区大小时会发生什么?

时间:2018-06-06 11:14:03

标签: c string strlen

我编写了以下代码以更好地理解strnlen的行为:

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

int main(int argc, char **argv)
{
    char bufferOnStack[10]={'a','b','c','d','e','f','g','h','i','j'};
    char *bufferOnHeap = (char *) malloc(10);

    bufferOnHeap[ 0]='a';
    bufferOnHeap[ 1]='b';
    bufferOnHeap[ 2]='c';
    bufferOnHeap[ 3]='d';
    bufferOnHeap[ 4]='e';
    bufferOnHeap[ 5]='f';
    bufferOnHeap[ 6]='g';
    bufferOnHeap[ 7]='h';
    bufferOnHeap[ 8]='i';
    bufferOnHeap[ 9]='j';

    int lengthOnStack = strnlen(bufferOnStack,39);
    int lengthOnHeap  = strnlen(bufferOnHeap, 39);

    printf("lengthOnStack = %d\n",lengthOnStack);
    printf("lengthOnHeap  = %d\n",lengthOnHeap);

    return 0;
}

请注意两个缓冲区中故意缺少空终止。 根据文档,似乎长度应该 都是39:

  

返回值          strnlen()函数返回strlen(s),如果小于maxlen,则返回          maxlen如果第一个maxlen字符中没有空终止('\ 0')          s指出。

这是我的编译行:

$ gcc ./main_08.c -o main

输出:

$ ./main
lengthOnStack = 10
lengthOnHeap  = 10

这里发生了什么?谢谢!

4 个答案:

答案 0 :(得分:3)

首先,don't cast malloc

其次,您正在阅读数组的末尾。数组边界外的内存是未定义的,因此无法保证它不为零;在这个例子中,它是!

一般来说,这种行为是草率的 - 请参阅this answer以了解潜在后果的详细摘要

答案 1 :(得分:3)

首先,strnlen()未由C标准定义;它是POSIX标准功能。

话虽如此,请仔细阅读文档

  

strnlen()函数返回s指向的字符串中的字节数,不包括终止空字节('\ 0'),但最多只能maxlen。在执行此操作时,strnlen()仅查看maxlen处的前s个字节,而不会超出s+maxlen

这意味着,在调用函数时,您需要确保为maxlen提供的值,对于提供的字符串,数组idexing对[maxlen -1]有效,即< em> string 中至少有maxlen个元素。

否则,在访问字符串时,您将冒险进入未分配给您的内存位置(数组越界访问),特此调用 undefined behaviour

请记住,此函数用于计算数组的长度,上限为值maxlen)。这意味着,提供的数组至少等于或大于边界,而不是相反。

[脚注]:

根据定义,字符串以空值终止。

引用C11,章节§7.1.1,术语定义

  

字符串是由第一个空值终止并包含第一个空值的连续字符序列   字符。 [...]

答案 2 :(得分:1)

您的问题大致相当于以下内容:

  

我知道防盗警报应该可以防止你的房子被抢劫。今天早上离开家时,我关掉了防盗报警器。在我离开的那天的某个时候,一个窃贼闯入并偷走了我的东西。这是怎么发生的?

或者对此:

  

我知道您可以使用汽车上的巡航控制来帮助您避免获得超速罚单。昨天我在一条速度限制为65的道路上行驶。我将巡航控制设置为95.一名警察拉我过来,我得到了一张超速罚单。这是怎么发生的?

实际上,这些都不对。这是一个更人为的比喻:

  

我住在一条有10码长的车道通往街道的房子里。我训练了我的狗来取我的报纸。有一天,我确定车道上没有报纸。我把我的狗放在一个39码的皮带上,我告诉他要取新闻报道。我希望他能在39码外的皮带末端走。但相反,他只走了10码,然后停了下来。这是怎么发生的?

当然有很多答案。也许,当你的狗走到你的无报纸车道尽头时,他立即在排水沟里发现了别人的报纸。或者,也许,当皮带未能阻止他在车道尽头并继续进入街道时,他被车撞倒了。

将你的狗放在皮带上的目的是将他限制在一个安全区域 - 在这种情况下,你控制的财产。如果你把他放在一条长长的皮带上,以至于他可以走到街上,或者进入树林里,你可以通过把他放在皮带上来挫败控制他的目的。

同样地,strnlen的整个要点是,如果在您定义的缓冲区内没有strnlen找到的空字符,则表现得很优雅。

非空终止字符串的问题是像strlen这样的函数(它盲目地搜索空终止符)从最后开始航行并在未定义的内存中盲目搜索,拼命地试图找到终结符。例如,如果你说

char non_null_terminated_string[3] = "abc";
int len = strlen(non_null_terminated_string);

行为未定义,因为strlen结束了。解决此问题的一种方法是使用strnlen

char non_null_terminated_string[3] = "abc";
int len = strnlen(non_null_terminated_string, 3);

但是如果你把更大的数字交给strnlen,它就会打败整个目的。你回想起当strnlen结束时会发生什么,并且没有办法回答这个问题。

答案 3 :(得分:0)

当...... &#34; Undefined behaviour (UB)&#34; 时会发生什么?

  

“当编译器遇到[给定的未定义构造]时,让恶魔飞出你的鼻子是合法的”

您的标题实际上不是 UB ,因为调用strnlen("hi", 5)是完全合法的,但您问题的细节显示它确实是UB ...... < / p>

strlenstrnlen都需要一个字符串,即一个以空字符结尾的char序列。向函数提供非空终止的char数组 UB

在您的情况下,该功能会读取前10个char,找不到'\0',因为它没有越界它继续读取,并通过调用 UB (读取未分配的内存)。可能是你的编译器冒昧地使用'\0'结束你的数组,可能是'\0'之前存在......可能性仅受编译器设计者的限制。