为什么 malloc(sizeof(pointer)) 能够工作?

9

以下代码运行良好:

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

int main()
{
    struct node{
        int a, b, c, d, e;
    };
    struct node *ptr = NULL;
    printf("Size of pointer ptr is %lu bytes\n",sizeof (ptr));
    printf("Size of struct node is %lu bytes\n",sizeof (struct node));
    ptr = (struct node*)malloc(sizeof (ptr));               //Line 1
//    ptr = (struct node*)malloc(sizeof (struct node));    //Line 2

    ptr->a = 1; ptr->b = 2; ptr->c = 3; ptr->d = 4; ptr->e = 5;
    printf("a: %d, b: %d, c: %d, d: %d, e: %d\n",
            ptr->a,ptr->b,ptr->c,ptr->d,ptr->e);
    return 0;
}

当编译为:
gcc -Wall file.c

我的问题是:为什么这样没问题? malloc 函数根据参数所指定的字节数分配内存空间,这里在我使用的64位Linux机器上,sizeof ptr 是8个字节。我认为 malloc 只会提供8个字节,但它又如何访问所有的变量a、b、c、d、e呢?是只有GCC才可以还是我对标准C理解有误?
据我所知,“Line 2”应该替换“Line 1”,但两行都可以正常工作。为什么呢?

6
以下代码也可能可以正常工作:int* x = (int*)rand(); *x = 123;,但这并不意味着这是一个好主意。 - John Ledbetter
C/C++ 结合使用 malloc 时不会检查或验证大小是否与分配结果所赋值的指针底层类型的大小匹配 - 这取决于您自己。 - Richard Sitze
2
未定义行为是未定义的。 它可能会崩溃,可能会似乎成功,或者可能会删除您的硬盘。 您永远无法确定在调用未定义行为的那一刻会发生什么,包括读取/写入超出分配的内存范围。 - Adam Rosenfield
我很想说:因为它不是Java。在C中,只要你停留在进程内存中,你就可以随意写入任何地方(只要页面不是只读的)。 - bestsss
4个回答

11

这里存在未定义行为。

malloc将分配8个字节(正如您所说),但这个转换是“不好的”:

ptr = (struct node*)malloc(sizeof (ptr));

在这行代码之后,ptr将会指向一个内存块,该内存块只分配了8个字节,其余是一些“随机”的字节。因此,进行

ptr->a = 1; ptr->b = 2; ptr->c = 3; ptr->d = 4; ptr->e = 5;

实际上,您更改了一些内存,不仅仅是由malloc分配的内存。

换句话说,您正在重写某些内存,而不应该接触它们。


8

第一行代码是错误的,不会分配足够的空间。如果您稍后可以访问结构成员,那只是因为C语言没有阻止您访问不属于您的内存。

在没有为整个结构体分配足够空间时访问ptr->bptr-c等是未定义的行为,下次运行代码时可能会崩溃,或者您可能会覆盖程序的另一个部分中的数据。

为了说明问题,立即在第一个结构体后分配第二个struct node。这并不是保证可以说明问题,但您很可能会看到类似以下结果:

struct node *ptr = NULL;
struct node *ptr2 = NULL;

ptr = (struct node*)malloc(sizeof (ptr));  // Your Line 1
ptr2 = malloc(sizeof(struct node));        // alloc another struct on the heap

ptr->a = 1; ptr->b = 2; ptr->c = 3; ptr->d = 4; ptr->e = 5;
ptr2->a = 11; ptr->b = 12; ptr->c = 13; ptr->d = 14; ptr->e = 15;

printf("ptr:  a: %d, b: %d, c: %d, d: %d, e: %d\n",
        ptr->a, ptr->b, ptr->c, ptr->d, ptr->e);
printf("ptr2: a: %d, b: %d, c: %d, d: %d, e: %d\n",
        ptr2->a, ptr2->b, ptr2->c, ptr2->d, ptr2->e);

输出:

ptr:  a: 1, b: 2, c: 3, d: 4, e: 11
ptr2: a: 11, b: 12, c: 13, d: 14, e: 15

请注意,ptr2->a的赋值已经修改了ptr->e,因此您可以看到一个错误分配的结构正在侵占另一个内存。这肯定不是您想要的结果。

如果删除第一行,程序会出现分段错误,因为没有内存,所以无法访问结构体成员。也许我没有理解你的意思。 - Bharat Kul Ratan
2
不为结构体分配内存和分配不足的内存同样是错误的,尽管第二种情况可能更容易导致崩溃。仅仅因为第一行“看起来”工作正常,并不意味着它是正确的。请始终使用第二行。 - pb2q

3

malloc 只分配了8个字节,但这并不能阻止你访问超出这个范围的内存。你很可能会破坏堆栈,并可能覆盖其他对象。程序的行为是未定义的。


2
在代码段 ptr->a = 1; ptr->b = 2; ptr->c = 3; ptr->d = 4; ptr->e = 5; 中,您正在访问超出 malloc 分配的内存范围。
这是一种 缓冲区溢出 的情况,会导致未定义的行为

这是因为

 C and C++ provide no built-in protection against accessing or overwriting data
in any part of memory.

所以,有时它可能有效,但有时也可能会导致程序崩溃。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接