神秘的内存管理

Mysterious memory management

我正在玩简单的缓冲区溢出。但是,我发现这样的编译器行为非常有趣:

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

void func(char *arg1) {
    int authenticated = 0;

    char buffer[4];

    strcpy(buffer, arg1);

    if(authenticated) {
        printf("HACKED !\n");
    } else {
        printf("POOR !\n");
    }
    return;
}

int main() {
    char* mystr = "abcdefghijkl";
    func(mystr);
    printf("THANK YOU!\n");
    return 0;
}

让我感到奇怪的是,我需要将 13 元素的缓冲区分配给 arg1,而不是 5 元素的缓冲区才能覆盖已验证的变量。

GDB 确认:

(gdb) print &authenticated 
 = (int *) 0x7fffffffe75c
(gdb) print &buffer
 = (char (*)[4]) 0x7fffffffe750

地址之间相差12。 为什么在这种情况下编译不是最优的?

在重构这个函数的情况下,差异在变化,但为什么差异不总是 4,这似乎是最佳解决方案。

谢谢

您正在调用未定义的行为。任何事情都可能发生。

优化编译器会注意到在strcpy之后没有使用buffer,所以strcpy操作可以去掉。如果没有未定义的行为,它不会有任何可检测的副作用。

优化(或非优化)编译器会注意到 "authenticated" 始终为 0 并且永远不会更改,除非存在未定义的行为,并且编译器始终可以假设没有未定义的行为。所以总是打印 "POOR !\n".

绝对没问题

因此,在存在未定义行为的情况下,您尝试从实验中得出的任何结论都是 100% 没有根据的。

变量之间的距离比您预期的要大,因为它们 aligned to optimize performance。一些操作要求变量的内存位置是某个数字(通常是变量的大小)的整数倍。因此,例如,一个 8 字节 double 可以放在内存中的位置 0x1000 或 0x1008,但不能放在 0x1004。这是您的堆栈最终的样子(缺少优化等),数字表示距堆栈底部的偏移量:

-16: char[] buffer (4 bytes)
-12: padding (8 bytes)
 -4: int authenticated (4 bytes)

int 以 4 字节对齐是可以理解的,但为什么 char 缓冲区以 16 字节对齐?为了能够利用 SSE instructions 进行字符串操作。这些需要 16 位内存对齐。在禁用 SSE 的情况下编译程序(-mno-sse 使用 gcc)导致此布局:

 -8: char[] buffer (4 bytes)
 -4: int authenticated (4 bytes)

所以这证实了额外的填充是由于 SSE。