为什么此代码中的缓冲区溢出与我的预期不同?

时间:2017-05-13 14:46:08

标签: c memory memory-management output buffer

我有这个程序:

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

void main(void) {
    char *buffer1 = malloc(sizeof(char));
    char *buffer2 = malloc(sizeof(char));

    strcpy(buffer2, "AA");

    printf("before: buffer1 %s\n", buffer1);
    printf("before: buffer2 %s\n", buffer2);

    printf("address, buffer1 %p\n", &buffer1);
    printf("address, buffer2 %p\n", &buffer2);

    strcpy(buffer1, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

    printf("after: buffer1 %s\n", buffer1);
    printf("after: buffer2 %s\n", buffer2);
}

打印哪些:

before: buffer1 
before: buffer2 AA
address, buffer1 0x7ffc700460d8
address, buffer2 0x7ffc700460d0
after: buffer1 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
after: buffer2 B

我希望这段代码能做什么:

  • 由于char是8位长,我希望两个缓冲区的大小都是1字节/ 8位。

  • 一个ASCII字符长7位,我希望每个缓冲区都有两个字符。

  • 由于我直接在一个字节之后分配两个缓冲区,我希望它们在内存中直接相邻。因此,我希望每个地址之间的差异为1(因为内存是通过字节来寻址的?),而不是因为我的小程序打印了。

  • 因为它们在内存中彼此紧挨着,所以当我BB写入第一个strcpy(buffer1, BBBB);时,我希望缓冲区2被BB溢出buffer1,其余的溢出到buffer2。因此,我希望strcpy(buffer1, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");产生:

    • buffer2中的缓冲区溢出,因此它的值为BBBBBBBBBBBBBBBBBBBBBBBBBBBBB左右。

      • 我是如何计算出来的:B的amonut已经为两个缓冲区提供了strcpy&#39; d - 4 B&#39}。
    • 分段错误。我只分配了2个字节(因为buffer1buffer2的大小在一起2个字节)。由于BBBBBBBBBBBBBBBBBBBBBBBBB既不适合buffer1也不适合buffer2(因为两者都已填充),因此会在buffer2之后溢出到下一个内存缓冲区。而且由于我没有分配,我预计会出现分段错误。

因此,我想问:为什么我的计划与我的期望不同?我在哪里误解了事情?

我有一个x86_64架构,上面的程序是用gcc version 6.3.1 20170306 (GCC)编译的

我不要求:

  • 我知道strcpy没有绑定检查,并且用法是故意的。我想调查缓冲区溢出等。因此,请不要写一个答案/评论说我应该使用不同的方法strcpy

3 个答案:

答案 0 :(得分:3)

首先,请阅读What should main() return in C and C++?

现在关注如何分配内存。

How much memory does malloc(1) allocate?

  

8字节的开销被添加到我们对单个字节的需求,并且   总数小于32的最小值,这就是我们的答案:   malloc(1)分配32个字节。

使你的基础柔和。

注意:malloc(1)分配32个字节对​​于该链接上讨论的实现可能是正确的,但它非常依赖于实现,并且会有所不同。

另一方面,如果你做了:

char buffer1[1], buffer2[1];

而不是动态分配内存,您会看到不同的结果。例如,在我的系统中:

Georgioss-MacBook-Pro:~ gsamaras$ ./a.out // with malloc
before: buffer1 
before: buffer2 AA
address, buffer1 0x7fff5ecb6bd8
address, buffer2 0x7fff5ecb6bd0
after: buffer1 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
after: buffer2 BBBBBBBBBBBBBBBBB
Georgioss-MacBook-Pro:~ gsamaras$ gcc -Wall main.c // no malloc
Georgioss-MacBook-Pro:~ gsamaras$ ./a.out 
Abort trap: 6

提示:规模尚未正式调整;访问超出请求大小的字节有未定义行为。 (如果它被正式舍入,则会有实现定义的行为。)

答案 1 :(得分:2)

  
      
  • 因为char是8位长,...
  •   

这对于所述的体系结构和操作系统是正确的。 (C标准允许char 更多而不是8位长,但现在这种情况非常罕见;我所知道的唯一例子是TMS320系列DSP,其中char可能是16位。它不允许更小。)

请注意,sizeof(char) == 1 按照定义,因此在代码中编写sizeof(char)foo * sizeof(char)通常被视为不良风格。

  

...我希望两个缓冲区的大小都是1字节/ 8位。

这也是正确的(但见下文)。

  
      
  • 一个ASCII字符长7位,我希望每个缓冲区都有两个字符。
  •   

这是不正确的,原因有两个。首先,没有人再使用7位ASCII。事实上每个字符长八个位。其次,两个七位字符适合一个八位缓冲区。我看到在这个问题的评论中存在一些混淆,所以让我尝试进一步解释:七位可以代表2 7 不同的值,只有足够的空间来定义128个不同的字符按原始ASCII标准。两个七位字符一起可以具有128 * 128 = 16384 = 2 14 个不同的值;这需要14位来表示,并且不适合8位。你似乎认为它只有2 * 128 = 2 8 ,这将适合8位,但这不对;这意味着一旦你看到第一个字符,第二个字符只有两个的可能性,而不是128个。

  
      
  • 当我直接在一个字节之后分配两个缓冲区时,我希望它们在内存中直接相邻。因此,我希望每个地址之间的差异为1(因为内存是由字节寻址的?),而不是我的小程序打印的8。
  •   

正如您自己观察到的那样,您的期望是错误的。

malloc不需要将连续分配放在一起;事实上,&#34;这些分配是彼此相邻的&#34;可能不是一个有意义的问题。 C标准不遗余力地避免要求在两个不指向同一阵列的指针之间进行任何有意义的比较。

现在,您正在开发一个具有&#34;平面地址空间&#34;的系统,因此 对于比较来自连续分配的指针是有意义的(前提是您在自己的脑中进行此操作) ,而不是代码)并且对于分配之间的差距有一个合理的解释,但首先我必须指出你打印了错误的地址:

printf("address, buffer1 %p\n", &buffer1);
printf("address, buffer2 %p\n", &buffer2);

这将打印指针变量的地址,而不是缓冲区的地址。你应该写的

printf("address, buffer1 %p\n", (void *)buffer1);
printf("address, buffer2 %p\n", (void *)buffer2);

(强制转换为void *是必需的,因为printf采用变量参数列表。)如果您已经写过,您会看到类似于

的输出
address, buffer1 0x55583d9bb010
address, buffer2 0x55583d9bb030

并且需要注意的重要一点是,这些分配相差 16个字节,不仅如此,它们都可以被16整除。

malloc需要根据任何类型的要求生成对齐的缓冲区,即使您不能使用该类型的值进入分配。如果地址可以被该数字整除,则该地址与某些字节数对齐。在您的系统上,最大对齐要求为16;您可以通过运行此程序来确认...

#include <stdalign.h>
#include <stddef.h>
#include <stdio.h>
int main(void) { printf("%zu\n", alignof(max_align_t)); return 0; }

这意味着malloc返回的所有地址必须可被16整除。因此,当您向malloc询问两个单字节缓冲区时,它们之间必须留下15个字节的间隔。 意味着malloc缩小了尺寸; C标准专门禁止您访问间隙中的字节。 (我不知道任何现代商业CPU可以强制执行该禁令,但调试工具如valgrind会,并且已经有实验性的CPU设计可以做到这一点。而且,通常是之前的空间或malloc块包含malloc实施内部使用的数据后,您不得篡改。)

第二次分配后存在类似的差距。

  
      
  • 由于它们在内存中直接相邻,我希望缓冲区2在BB strcpy(buffer1, BBBB);时溢出,因为第一个BB被写入buffer1其余的溢出到buffer2
  •   

如前所述,它们在内存中并不直接相邻,每个B占用 8个位。一个B写入您的第一个分配,下一个15分配给两个分配之间的差距,第16个分配到第二个分配,15个之后写入差距之后第二个分配,最后一个分配B和一个NUL到了以外的空间。

  

我只分配了2个字节(因为buffer1buffer2的大小在一起2个字节)。由于BBBBBBBBBBBBBBBBBBBBBBBBB既不适合buffer1也不适合buffer2(因为两者都已填充),因此会在buffer2之后溢出到下一个内存缓冲区。而且由于我没有分配,我预计会出现分段错误。

我们已经讨论过为什么你的计算不正确,但你做了在第二次分配之后一直写到差距的末尾并进入&#34;以后的空间# 34;,那么为什么没有段错?这是因为,在操作系统原语级别,内存被分配给名为&#34; pages&#34;的单元中的应用程序,这些单元大于您要求的内存量。 CPU只能检测到缓冲区溢出,并在超限跨越页边界时触发分段错误。你还没走得太远。我在我的计算机上试验了你的程序,这是非常相似的,我需要写 132千字节(千字节是1024字节)(有人说这应该被称为a kibibyte;它们是错误的)超出buffer1的末尾以获得段错误。我的计算机上的页面每个只有4千字节,但是malloc要求操作系统提供更大块的内存,因为系统调用很昂贵。

没有得到提示段错误意味着你是安全的;你有很好的机会破坏malloc的内部数据,或者超出&#34;空间内某处的其他分配。如果我使用原始程序并在最后添加了对free(buffer1)的调用,则会在那里崩溃。

答案 2 :(得分:0)

malloc不保证内存中的位置。即使使用背靠背调用内存空间也是连续的,您也无法确定。此外,malloc通常会分配超出必要的空间。您的代码可能会出现段错误,但无法保证。

带有printf说明符的

%s打印指针中的字符,直到遇到NUL(ASCII 0)字符。

请记住,缓冲区溢出是未定义的行为,这意味着:您不确切知道会发生什么。