【问题标题】:I'm new to C, can someone explain why the size of this string can change?我是 C 新手,有人可以解释为什么这个字符串的大小会改变吗?
【发布时间】:2010-11-26 04:53:06
【问题描述】:

我从来没有真正做过很多 C 但我开始玩弄它。我正在写一个像下面这样的小sn-ps,试图理解C中关键构造/函数的用法和行为。下面我写的一个试图理解char* stringchar string[]之间的区别以及然后长度字符串工作。此外,我想看看sprintf 是否可用于连接两个字符串并将其设置为第三个字符串。

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

不过,出于好奇,我尝试将“连接”字符串扩展为比我分配的大小更长。令我惊讶的是,它仍然有效,并且字符串大小增加了,可以printf'd 罚款。

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

#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));
}

【问题讨论】:

    标签: c string char printf


    【解决方案1】:

    这一行是错误的:

    char str3[length3];
    

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

    char str3[length3+1];
    

    您还尝试获取 str3 的长度,但尚未设置。

    另外,这一行:

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

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

    【讨论】:

      【解决方案2】:
      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&lt;--------------------&gt;Dood\0" 复制到 7 个字符的数组中。那不合适。当然 C 函数不知道这一点,它只是复制到数组的末尾。未定义的行为,可能会崩溃。

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

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

      【讨论】:

        【解决方案3】:

        与其尝试通过反复试验来学习 C,我建议您去当地的书店购买一本“C 编程入门”书。这样你最终会更好地了解这门语言。

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

        【讨论】:

        • 我身边有一对 :) -- 不过我喜欢尝试 -- 如果我对某件事的工作原理感到好奇,我会尝试找出答案。另外,虽然我感谢您推荐学习 C 的方式,但这并不能真正回答问题......
        【解决方案4】:

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

        C 不执行数组边界检查。这意味着您调用的函数盲目地相信您为字符串分配了足够的空间。如果不是这种情况,您最终可能会超出为字符串分配的内存范围。对于堆栈分配的字符数组,您将覆盖局部变量的值。对于堆分配的 char 数组,您可以写入超出应用程序的内存区域。无论哪种情况,最好的情况是您会立即出错,最坏的情况是看似正常,但实际上并没有。

        至于作业,不能这样写:

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

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

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

        分配 n+1 个字符的存储空间并将指向该存储空间的指针存储在 str 中。当然,为了安全起见,您应该检查 malloc 是否返回 null。完成后,您需要调用 free(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 程序的全部内容,因此希望这个答案在很多方面都不准确和不完整,这无疑会在 cmets 中指出。 ;-)

        【讨论】:

          【解决方案5】:

          您的str3 太短 - 您需要为空终止符和“”字符串的长度添加额外的字节字面意思。

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

          行为未定义,因此它可能会或可能不会出现段错误。

          【讨论】:

            【解决方案6】:

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

            char str3[length3 + 1];
            

            ……你应该准备好了。

            【讨论】:

              【解决方案7】:

              C 字符串以 '\0' 结尾,需要一个额外的字节,所以至少你应该这样做

              char str3[length3 + 1]
              

              会做的。

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                  【解决方案9】:

                  几个要点:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-01-10
                    • 2022-01-12
                    • 1970-01-01
                    • 2022-01-22
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-08-31
                    相关资源
                    最近更新 更多