代码之家  ›  专栏  ›  技术社区  ›  ryuk

C中的字符和字符串

  •  -3
  • ryuk  · 技术社区  · 10 年前

    我遇到了一个问题的一部分,在这个问题中,我得到了一个输出,但我需要一个解释,为什么它是真的,并且有效?

    char arr[4]; 
    strcpy(arr,"This is a link"); 
    printf("%s",arr);
    

    当我编译和执行时,我得到了以下输出。 输出:

    This is a link
    
    5 回复  |  直到 10 年前
        1
  •  1
  •   David C. Rankin    10 年前

    简而言之,(那一次)它之所以奏效,是因为你很幸运。超出数组末尾的写入是 未定义的行为 哪里 未定义的行为 就是这样, 未定义 ,它很容易导致分割错误,因为它会产生输出。(尽管通常会导致堆栈损坏)

    在C中处理字符数组时, 你 负责确保您分配了足够的存储空间。当您打算将数组用作字符串时,还必须为 每个字符 +1 对于 nul终止 字符结尾(这正是 nul终止 字符串(C)。

    为什么它有效?通常,当你要求时,说 char arr[4]; 编译器只保证为 arr 然而,根据编译器、对齐方式等,编译器实际上可能会分配它用作 最低分配单位 到 棱 也就是说,虽然你只要求 4-bytes 而且只能保证 4-usable-bytes ,编译器可能实际上已将 8, 16, 32, 64, or 128, etc-bytes .

    或者,你很幸运 棱 是上次请求的分配,但尚未请求或写入从开始的内存地址 byte-5 下列的 棱 内存中。

    重点是,你要求 4字节 而且只能保证 4字节 可获得的是的,它可能在那个里起作用 printf 在你的代码中发生任何其他事情之前,但是你的代码是完全不可靠的,你正在玩带有堆栈损坏的俄罗斯轮盘赌(如果还没有发生的话)。

    在C语言中,确保代码、存储和内存使用完全由您负责 定义明确的 而且你不会迷失在 未定义 ,因为如果你这样做,所有的赌注都会被打掉,你的代码不值得它所存储的字节。

    您如何编写代码 定义明确的 ? 适当地限制和验证代码中的每个必需步骤。对于代码段,可以使用 strncpy 而不是 strcpy 然后肯定地说 nul终止 棱 呼叫之前 打印 ,例如。

    char arr[4] = "";                           /* initialize all values */
    strncpy(arr,"This is a link", sizeof arr);  /* limit copy to bytes available */
    arr[sizeof arr - 1] = 0;                    /* affirmatively nul-terminate   */
    printf ("%s\n",arr);
    

    现在,您可以依赖 棱 在剩下的代码中。

        2
  •  0
  •   Ofir Sadeh    10 年前

    您的代码有一些内存问题(缓冲区溢出) strcpy 将字节复制到空字符。功能 printf 打印到空字符。

        3
  •  0
  •   Nandin Borjigin    10 年前

    这段代码的行为没有保证。 就像:你告诉我“我下午5点来接你”,你来的时候我会在那里(保证)。但我不能保证我是否给你带了一杯咖啡,因为你没有告诉我你想要一杯。也许我很好,买了两杯咖啡,或者我是个小气鬼,只给自己买了一杯。

        4
  •  0
  •   paxdiablo    10 年前

    它可能会起作用。可能不会。它可能会立即明显失败。它可能会在未来的某个时候以微妙的方式失败,这会让你发疯。

    这就是未定义行为的阴险性质。不要这样做。

    如果它真的有效,那完全是偶然的,没有任何保证。它是 可能的 覆盖堆栈或其他内存中的内容(取决于实现和实际变量的方式/位置 str 已定义 (a) )但被覆盖的内存在那之后就不再使用了(考虑到代码的简单性)。

    它可能在 不 这种方式使它成为一个好主意。


    对于我们当中的语言律师 J.2 (未定义行为的实例) C11 明确规定:

    数组下标超出范围,即使对象显然可以用给定的下标访问(如左值表达式 a[1][7] 给出声明 int a[4][5] ).

    那个 提供有用信息的 截面参考 6.5.6 ,这是规范性的,在讨论指针/整数加法(其中 a[b] 是一个示例):

    如果指针操作数和结果都指向同一数组对象的元素,或者超过数组对象的最后一个元素,则求值不应产生溢出; 否则,行为是未定义的。 如果结果超过数组对象的最后一个元素,则不应将其用作被计算的一元*运算符的操作数。


    (a) 例如,在我的系统中,在内部声明变量 main 导致程序崩溃,因为缓冲区溢出会破坏堆栈上的返回地址。

    但是,如果我将声明放在文件级别( 主要的 ),它似乎运行得很好,打印消息然后退出程序。

    但我向你保证,这只是因为你扔掉的内存对程序的继续不重要 在这种情况下。 几乎可以肯定,它在任何比这个例子更重要的事情上都是重要的。

        5
  •  -1
  •   Ashwani    10 年前

    只要printf放在strcpy后面,代码就始终可以工作。但这是错误的编码 尝试跟随,但它不会起作用 int j; char arr[4]; int i; strcpy(arr,"This is a link"); i=0; j=0; printf("%s",arr); 要理解为什么会这样,您必须了解堆栈的概念。所有局部变量都在堆栈上分配。因此,在您的代码中,程序控制为“arr”分配了4个字节,当您复制大于4个字节的字符串时,您正在覆盖/损坏其他一些内存。但当您在strcpy之后访问“arr”时,您覆盖的区域可能属于其他一些变量,但程序仍然没有更新,这就是为什么您的printf工作正常的原因。但是,正如我在示例代码中建议的那样,如果其他变量被更新并落入您覆盖的内存区域,您将无法获得正确的(或更合适的)输出 您的代码也在运行,因为堆栈向下增长,如果它是另一种方式,那么您也没有得到所需的输出

    推荐文章