我是C的新手,有人可以解释为什么这个字符串的大小可以改变吗?

时间:2009-09-07 10:58:10

标签: c string char printf

我从来没有真正做过多少C但是我开始玩它了。我正在编写如下所示的小片段来尝试理解C中关键构造/函数的用法和行为。下面我写了一篇试图理解char* stringchar string[]之间的区别以及如何字符串长度有效。此外,我想看看sprintf是否可用于连接两个字符串并将其设置为第三个字符串。

我发现用于存储其他两个串联的第三个字符串必须使用char string[]语法设置,否则二进制文件将以SIGSEGV (Address boundary error)消亡。使用数组语法设置它需要一个大小,所以我最初通过将其设置为其他两个字符串的组合大小来开始。这似乎让我足够好地执行连接。

出于好奇,我尝试将“连接”字符串扩展为比我分配的字符长。令我惊讶的是,它仍然有效,字符串大小增加,可能printf'很好。

我的问题是:为什么会发生这种情况,是无效还是存在风险/弊端?此外,当char str3[length3]行尝试执行时,为什么char str3[7]有效但sprintf导致“SIGABRT(中止)”?

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

void main() {
    char* str1 = "Sup";
    char* str2 = "Dood";

    int length1 = strlen(str1);
    int length2 = strlen(str2);
    int length3 = length1 + length2;

    char str3[length3];
    //char str3[7];

    printf("%s (length %d)\n", str1, length1);           // Sup (length 3)
    printf("%s (length %d)\n", str2, length2);           // Dood (length 4)
    printf("total length: %d\n", length3);               // total length: 7
    printf("str3 length: %d\n", (int)strlen(str3));      // str3 length: 6
    sprintf(str3, "%s<-------------------->%s", str1, str2); 
    printf("%s\n", str3);                                // Sup<-------------------->Dood

    printf("str3 length after sprintf: %d\n",            // str3 length after sprintf: 29
            (int)strlen(str3));
}

9 个答案:

答案 0 :(得分:9)

这一行错了:

char str3[length3];

您没有考虑终止零点。它应该是:

char str3[length3+1];

你也试图获得str3的长度,但尚未设置。

此外,这一行:

sprintf(str3, "%s<-------------------->%s", str1, str2);

将溢出为str3分配的缓冲区。确保分配足够的空间来容纳整个字符串,包括终止零。

答案 1 :(得分:6)

void main() {
    char* str1 = "Sup"; // a pointer to the statically allocated sequence of characters {'S', 'u', 'p', '\0' }
    char* str2 = "Dood"; // a pointer to the statically allocated sequence of characters {'D', 'o', 'o', 'd', '\0' }

    int length1 = strlen(str1); // the length of str1 without the terminating \0 == 3
    int length2 = strlen(str2); // the length of str2 without the terminating \0 == 4
    int length3 = length1 + length2;

    char str3[length3]; // declare an array of7 characters, uninitialized

到目前为止一切顺利。现在:

printf("str3 length: %d\n", (int)strlen(str3));      // What is the length of str3? str3 is uninitialized!

C是一种原始语言。它没有字符串。它具有的是数组和指针。字符串是约定,而不是数据类型。按照惯例,人们同意“字符数组是一个字符串,字符串以第一个空字符结束”。所有C字符串函数都遵循此约定,但它是一种约定。简单地假设您遵循它,否则字符串函数将会中断。

所以str3 不是一个7个字符的字符串。它是一个包含7个字符的数组。如果将它传递给需要字符串的函数,那么该函数将查找'\0'以查找字符串的结尾。 str3从未初始化,因此它包含随机垃圾。在你的情况下,显然,在第6个字符后面有一个'\0',所以strlen返回6,但这不能保证。如果它不存在,那么它将读取超过数组的末尾。

sprintf(str3, "%s<-------------------->%s", str1, str2); 

这里再次出错了。您正在尝试将字符串"Sup<-------------------->Dood\0"复制到7个字符的数组中。那不合适。当然C函数不知道这个,它只是复制超过数组的末尾。未定义的行为,可能会崩溃。

printf("%s\n", str3);  // Sup<-------------------->Dood

在这里,您尝试打印存储在str3的字符串。 printf是一个字符串函数。它不关心(或知道)数组的大小。它被赋予一个字符串,并且像所有其他字符串函数一样,通过查找'\0'来确定字符串的长度。

答案 2 :(得分:3)

我没有尝试通过反复试验来学习C,而是建议您去当地的书店购买“C编程入门”一书。你最终会以这种方式更好地了解语言。

没有什么比一半了解C的程序员更危险了!

答案 3 :(得分:3)

你必须要了解的是C实际上没有字符串,它有字符数组。此外,字符数组没有相关的长度信息 - 相反,字符串长度是通过迭代字符直到遇到空字节来确定的。这意味着,每个char数组的长度至少应为strlen + 1个字符。

C不执行数组边界检查。这意味着您调用的函数盲目地信任您为字符串分配了足够的空间。如果不是这种情况,您最终可能会超出为字符串分配的内存范围。对于堆栈分配的char数组,您将覆盖局部变量的值。对于堆分配的char数组,您可以在应用程序的内存区域之外编写。在任何一种情况下,最好的情况是你会立即出错,最糟糕的情况是出现的东西正在工作,但实际上并非如此。

至于作业,你不能写这样的东西:

char *str;
sprintf(str, ...);

并期望它工作 - str是一个未初始化的指针,因此值“未定义”,这在实践中意味着“垃圾”。指针是内存地址,因此尝试写入未初始化的指针是尝试写入随机内存位置。不是个好主意。相反,你想要做的是:

char *str = malloc(sizeof(char) * (string length + 1));

分配n + 1个字符的存储空间,并将指针存储在str中。当然,为了安全起见,您应该检查malloc是否返回null。当你完成后,你需要免费打电话(str)。

你的代码使用数组语法的原因是因为作为局部变量的数组是自动分配的,所以实际上有一个免费的内存片段。那是(通常)不是未初始化指针的情况。

关于字符串大小如何变化的问题,一旦你理解了关于空字节的位,就会发现很明显:改变字符串大小所需要做的就是使用空字节。例如:

char str[] = "Foo bar";
str[1] = (char)0; // I'd use the character literal, but this editor won't let me

此时,strlen报告的字符串长度恰好为1.或者:

char str[] = "Foo bar";
str[7] = '!';

之后strlen可能会崩溃,因为它会继续尝试从数组边界之外读取更多字节。它可能会遇到一个空字节然后停止(当然,返回错误的字符串长度),否则它可能会崩溃。

我已经写了所有一个C程序,所以希望这个答案在很多方面都是不准确和不完整的,这无疑会在评论中指出。 ; - )

答案 4 :(得分:1)

你的str3太短了 - 你需要为null-terminator和“&lt; --------------------”的长度添加额外的字节&gt;”中string literal。

  

出于好奇,我试过了   将“连接”字符串扩展为   比我的尺寸长   分配。令我惊讶的是,它   仍然工作和字符串大小   增加并且可以打印出来。

行为未定义,因此可能会或可能不会发生段错误。

答案 5 :(得分:1)

strlen返回字符串的长度而没有尾随的NULL字节(\00x00),但是当您创建一个变量来保存组合字符串时,您需要添加1个字符

char str3[length3 + 1];

......你应该全力以赴。

答案 6 :(得分:1)

C字符串'\ 0'终止并且需要一个额外的字节,所以至少你应该

char str3[length3 + 1]

将完成这项工作。

答案 7 :(得分:0)

在sprintf()中,ypu正在写入超出为str3分配的空间。这可能会导致任何类型的未定义行为(如果你很幸运,那么它将崩溃)。在strlen()中,它只是从你指定的内存位置搜索一个NULL字符,它在第29个位置找到一个。它也可以是129,即它表现得非常不稳定。

答案 8 :(得分:0)

一些要点:

  • 仅仅因为它起作用并不意味着它是安全的。超过缓冲区的末尾总是不安全,即使它在您的计算机上运行,​​它也可能在不同的操作系统,不同的编译器或甚至第二次运行时失败。
  • 我建议您将char数组视为容器,将字符串视为对象,并将其存储在容器中。在这种情况下,容器必须比它所拥有的对象长1个字符,因为需要“空字符”来表示对象的结尾。容器是固定大小的,对象可以改变大小(通过移动空字符)。
  • 数组中的第一个 null字符表示字符串的结尾。数组的其余部分未使用。
  • 您可以在char数组中存储不同的东西(例如一系列数字)。这取决于你如何使用它。但是printf()strcat()之类的字符串函数假设在那里找到了以空字符结尾的字符串。