字符串不是以NULL结尾但仍然表现正常,为什么?

时间:2009-09-23 07:30:48

标签: c string pointers

在下面的代码中,我使用strncpy()将字符串复制到char * str,长度为10个字符。

现在根据strncpy()手册,“警告:如果src的前n个字节中没有空字节,则放在dest中的字符串将不会以空值终止。”这正是这里发生的事情。

源字符串长度为26个字符,我复制了10个字符,因此在str的末尾没有放置空字符。

但是当我打印str的内容时,从0开始直到我得到'\ 0',它表现正常。

为什么呢?当末尾没有'\ 0'时,为什么循环停在正确的位置?

我的理解是它应该给出“分段错误”,或者至少它不应该停在那里并继续打印一些垃圾值。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10

int main()
{
    char *str ;
    str = malloc( sizeof( char ) * SIZE );
    if( str == NULL ) 
        exit( 1 );
    memset( str, 0, sizeof( char ) * SIZE );

    strncpy( str, "abcdefghijklmnopqrstuvwxyz", sizeof( char ) * SIZE );

    unsigned int index;
    for( index = 0; str[ index ] != '\0' ; index++ ) {
        printf( "str[ %u ] has got : %c \n ", index, str[ index ] );
    }

    return 0;
}

这是输出:

 str[ 0 ] has got : a
 str[ 1 ] has got : b
 str[ 2 ] has got : c
 str[ 3 ] has got : d
 str[ 4 ] has got : e
 str[ 5 ] has got : f
 str[ 6 ] has got : g
 str[ 7 ] has got : h
 str[ 8 ] has got : i
 str[ 9 ] has got : j

任何帮助将不胜感激。

修改

是否有正确的方法来检查字符串是否以'\ 0'结尾?我一直认为上面的循环是最终的测试,但现在看来它不是。

让我们说我们从其他程序员开发的某个函数中获取一个字符串。现在我们怎么知道它以'\ 0'结束在正确的位置。可能它没有,那么它将超出实际大小,直到我们得到一些'\ 0'。我们永远无法知道字符串的实际大小。

那么我们如何解决这种情况呢?

有什么建议吗?

6 个答案:

答案 0 :(得分:15)

恰好在分配块的末尾之外有一个空字节。

最有可能malloc()分配更多内存并放置恰好包含空字节的所谓保护值,或者放置一些元数据以供free()稍后使用元数据恰好在该位置包含一个空字节。

无论如何,你不应该依赖这种行为。您必须为空字符请求(malloc())一个字节,以便合法地为您分配空字符位置。

没有可移植的方法来测试字符串是否正确地终止。可能会发生这样的情况:一旦你超过分配块的结束,你的程序就会崩溃。或者可能发生在块的末尾之外的某处存在空字符,并且在操作错误解释的字符串时,您将在块之后覆盖内存。

理想情况下,您需要一些函数来检查给定的地址是否分配给您属于与另一个给定地址相同的分配(可能是块的开始)。这将是缓慢的,不值得,并且没有标准的方法来做到这一点。

换句话说,如果遇到一个字符串,该字符串意味着以空值终止但实际上不是你被搞砸了 - 你的程序会遇到未定义的行为。

答案 1 :(得分:6)

至于你的编辑,我认为迂腐将有助于阐明一些问题。

在C中没有字符串这样的东西。有一个“C字符串”的概念是C标准库的工作原理,它被定义为NUL终止的字符序列,所以实际上没有“非空终止字符串”这样的东西“在C.所以你的问题更好地表达为”如何确定任意字符缓冲区是否是有效的C字符串?“或“我如何确定我找到的字符串是否是预期的字符串”

遗憾的是,第一个问题的答案是直接扫描缓冲区,直到遇到NUL字节为止。这将为您提供C字符串的长度。

第二个问题没有简单的答案。由于C没有具有长度元数据的实际字符串类型(或者能够跨函数调用来携带数组的大小),因此没有真正的方法来确定我们在上面确定的字符串长度是否为预期的字符串。很明显,如果我们开始在程序中看到段错误或者在输出中看到“垃圾”,但一般来说我们通过扫描直到第一个NUL字节(通常在字符串长度上有一个上限以避免混乱)来执行字符串操作缓冲区溢出错误)

答案 2 :(得分:4)

  

为什么会这样?

您分配的内存恰好在正确的位置有'\0'个字节。 (例如,如果你在调试模式下使用Visual C ++,堆管理器会在将内存交给你的程序之前分配内存。但它也可以是纯粹的运气。)

  

是否有正确的方法来检查字符串是否以'\0'结束?

没有。你需要你的字符串是零终止的(这是C std lib字符串处理函数所期望的)或者你需要在一个额外的变量中携带它们的长度。如果你没有这两个,你有一个错误。

  

现在我们怎么知道某个其他程序员开发的某些函数的某些字符串在'\0'的正确位置结束。也许它不会,然后它会超出实际大小,直到我们得到一些'\0'。我们永远无法知道字符串的实际大小。

     

那么我们如何解决这种情况呢?

你做不到。如果另一个功能将其拧得那么糟糕,那你就搞错了。

答案 3 :(得分:0)

Sharptooth已经解释了这种行为的可能原因,所以我不会重复这一点。

分配缓冲区时,我总是按字节过度分配,如下所示:

#define SIZE 10
char* buf = malloc(sizeof(char)*(SIZE+1));
/* error-check the malloc call here */
buf[SIZE] = '\0';

答案 4 :(得分:0)

你很幸运,没有超出分配的空间区域。

在所有其他平台上试用此代码,您会发现它的行为可能不同。

答案 5 :(得分:0)

我认为sharptooth的答案是正确的。分配的空间更多。我修改程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10

int main()
{
    char *str ;
    int *p;
    int actual_length;
    str = malloc( sizeof( char ) * SIZE );
    if( str == NULL ) 
        exit( 1 );

    actual_length = (int)*(str - 4) - 1 - 4;
    printf("actual length of str is %d\n", actual_length);
    p = (int*) malloc(sizeof(int));
    if (p == NULL) exit(1);
    *p = -1;
    char* pc = (char*)(p - 1);
    pc [0] = 'z';
    pc [1] = 'z';
    pc [2] = 'z';
    pc [3] = 'z';

    memset( str, 0, sizeof( char ) * SIZE );

    memcpy( str, "abcdefghijklmnopqrstuvwxyz", sizeof( char ) * SIZE );

    int i;
    for (i = SIZE; i < actual_length; i++)
     str[i] = 'y';

    unsigned int index;
    for( index = 0; str[ index ] != '\0' ; index++ ) {
        printf( "str[ %u ] has got : %c \n ", index, str[ index ] );
    }

    return 0;
}

输出

actual length of str is 12
str[ 0 ] has got : a 
 str[ 1 ] has got : b 
 str[ 2 ] has got : c 
 str[ 3 ] has got : d 
 str[ 4 ] has got : e 
 str[ 5 ] has got : f 
 str[ 6 ] has got : g 
 str[ 7 ] has got : h 
 str[ 8 ] has got : i 
 str[ 9 ] has got : j 
 str[ 10 ] has got : y 
 str[ 11 ] has got : y 
 str[ 12 ] has got : z 
 str[ 13 ] has got : z 
 str[ 14 ] has got : z 
 str[ 15 ] has got : z 
 str[ 16 ] has got : \377 
 str[ 17 ] has got : \377 
 str[ 18 ] has got : \377 
 str[ 19 ] has got : \377 

我的操作系统是Debian Squeeze / sid。