代码之家  ›  专栏  ›  技术社区  ›  K J Gor

C中strncpy的内存混淆

  •  4
  • K J Gor  · 技术社区  · 8 年前

    本周,我的同事讨论了一个关于记忆的问题:

    示例代码1:

    int main()
    {
        #define Str "This is String."
        char dest[1];
        char buff[10];
    
        strncpy(dest, Str, sizeof(Str));
        printf("Dest: %s\n", dest);
        printf("Buff: %s\n", buff);
    }
    

    输出:

    Dest: This is String.
    Buff: his is String.
    

    示例代码2:

    int main()
    {
        #define Str "This is String."
        char dest[1];
        //char buff[10];
    
        strncpy(dest, Str, sizeof(Str));
        printf("Dest: %s\n", dest);
        //printf("Buff: %s\n", buff);
    }
    

    输出:

    Dest: This is String.
    *** stack smashing detected ***: ./test terminated
    Aborted (core dumped)
    

    我不明白为什么在案例1中我会得到这样的输出?因为buff甚至没有在strncpy中使用,如果我对变量buff进行注释,它将给出检测到的堆栈崩溃,但输出为dest。 此外,对于buff,为什么我将输出作为“他的字符串”

    4 回复  |  直到 8 年前
        1
  •  4
  •   A.N    8 年前

    这是一个有趣的问题,我们都希望在某种程度上理解。此处出现的问题称为: 缓冲区溢出 该问题的副作用可能因系统而异(也称为 未定义行为 ). 为了向您解释可能发生的情况,让我们假设程序中变量的内存布局如下所示

    enter image description here

    注:上述表示仅用于理解,不显示任何架构的实际表示。 执行strncpy命令后,该内存区域的内容如下:

    enter image description here

    现在,当您打印buff时,您可以看到buf的起始地址中现在有“h”。printf开始打印,直到它找到一个超过buff内存区域的空字符。因此,当您打印buf时,会得到“his is String”。 但是,请注意,由于堆栈保护(依赖于系统/实现),程序1不会生成堆栈破坏错误。因此,如果您在不包含此代码的系统上执行此代码,程序1也将崩溃(您可以通过将Str增加为长字符串来测试)。

    在程序2的情况下,strncpy只是在从main写入返回地址时通过堆栈保护,因此会发生崩溃。

    希望这有帮助。

    P、 所有上述描述都是为了理解,没有显示任何实际的系统表示。

        2
  •  3
  •   chqrlie    8 年前

    C标准规定: strncpy 这种方式:

    7.24.2.4 strncpy 作用

    提要

    #include <string.h>
     char *strncpy(char * restrict s1,
          const char * restrict s2,
          size_t n);
    

    这个 strncpy 函数副本不超过 n s2 指向的数组 s1 .

    如果复制发生在重叠的对象之间,则行为未定义。

    如果数组指向 s2 n 字符,空字符被附加到指向的数组中的副本 s1 直到 n 所有的字符都已经写好了。

    退换商品

    这个 strncpy 函数返回 s1 .

    这些语义被广泛误解: strncpy 不是 安全 strcpy ,目标数组是 n 论点

    在您的示例中 n 未定义 因为字符写在目标数组的末尾之外。

    您可以看到,这是第一个示例,因为 buff dest 自动存储中的阵列(aka )并被覆盖 strncpy 编译器可以使用不同的方法,因此无法保证观察到的行为。

    我的建议是: 切勿使用此功能 .其他C专家如布鲁斯·道森的观点: Stop using strncpy already!

    您应该喜欢一个不太容易出错的函数,例如:

    // Utility function: copy with truncation, return source string length
    // truncation occurred if return value >= size argument
    size_t bstrcpy(char *dest, size_t size, const char *src) {
        size_t i;
        /* copy the portion that fits */
        for (i = 0; i + 1 < size && src[i] != '\0'; i++) {
            dest[i] = src[i];
        }
        /* null terminate destination unless size == 0 */
        if (i < size) {
            dest[i] = '\0';
        }
        /* compute necessary length to allow truncation detection */
        while (src[i] != '\0') {
            i++;
        }
        return i;
    }
    

    您可以在示例中这样使用它:

    int main(void) {
        #define Str "This is String."
        char dest[12];
    
        // the size of the destination array is passed
        // after the pointer, just as for `snprintf`
        bstrcpy(dest, sizeof dest, Str);
        printf("Dest: %s\n", dest);
        return 0;
    }
    

    This is a S
    
        3
  •  0
  •   taskinoor    8 年前

    strncpy(dest, Str, sizeof(Str));

    你的 dest 只有一个字节,所以这里您正在内存中写入您不应该写入的内容,这会调用未定义的行为。换句话说,任何事情都可能发生,这取决于编译器如何实现这些事情。

    buf 编写是编译器放置的地方 dest 缓冲器 .所以,当你写超过 dest 你正在写信给 缓冲器 .你什么时候评论出来 缓冲器 这会导致崩溃。

    但正如我之前所说,如果使用不同的编译器,甚至同一编译器的不同版本,您可能会得到完全不同的行为。

    总结:永远不要做任何调用未定义行为的事情。在里面 strncpy 你应该使用 sizeof(dest) sizeof(src) 并为目的地分配足够的内存,以便源数据不会丢失。

        4
  •  0
  •   mksteve    8 年前

    0. dest
    1. buff
    12. canary
    16. Return address
    

    什么时候 buff 如果存在,它可以保护金丝雀和返回地址免受损坏。

    这是未定义的行为(将更多数据写入 dest 比适合)。金丝雀内部有一个特殊的随机值,该值在函数启动时设置,并在执行返回指令之前进行测试。这为缓冲区溢出增加了某种形式的保护。

    未定义性质的例子是,由于没有金丝雀,程序可能因“非法指令@xxxxxx”而崩溃。 如果返回地址与变量位置分开,程序可能表现正常。

    在大多数当前CPU上,堆栈通常会沿负方向增长。此外,dest vs buff的位置取决于编译器。它可能已将它们切换到原来的位置,或者(例如)您删除了第二个printf,编译器可能已删除了 dest ,因为它可能已经决定它没有被正确使用。