在C语言中动态内存分配问题

5
我正在使用malloc编写某些代码,然后遇到了一个问题,所以我编写了一个测试代码,它实际上总结了整个混乱,如下:
# include <stdio.h>
# include <stdlib.h>
# include <error.h>

int main()     
{
     int *p = NULL;
     void *t = NULL;
     unsigned short *d = NULL;

     t = malloc(2);
     if(t == NULL) perror("\n ERROR:");
     printf("\nSHORT:%d\n",sizeof(short));
     d =t;
     (*d) = 65536;
     p = t; 
     *p = 65536;
     printf("\nP:%p: D:%p:\n",p,d);
     printf("\nVAL_P:%d ## VAL_D:%d\n",(*p),(*d));
     return 0;
  }
   Output:: abhi@ubuntu:~/Desktop/ad/A1/CC$ ./test

            SHORT:2
            P:0x9512008: D:0x9512008:
            VAL_P:65536 ## VAL_D:0

我正在使用malloc分配2个字节的内存。malloc返回一个void*指针,存储在一个void*指针“t”中。
然后声明了2个指针p-整数类型和d-短类型。然后我将t分配给它们两个(p = t和d = t),这意味着d和p都指向堆上相同的内存位置。
当我尝试将65536(2 ^ 16)保存到(* d)时,我得到了警告,指出大整数值被截断,这是预期的。现在我再次将65536(2 ^ 16)保存到(* p)中,这没有引起任何警告。
打印(* p)和(* d)时,我得到了不同的值(虽然每个正确地定义了自己的指针类型)。
我的问题是:
1. 尽管我使用malloc分配了2个字节(即16位)的堆内存,但我如何能够在这两个字节中保存65536(通过使用整数类型的指针p)?我有一种感觉,那就是void到int *指针的自动类型转换(在p = t中),所以是将t分配给p导致访问超出通过malloc分配的内存区域吗?
2. 即使发生所有这些情况,为什么通过(* p)和(* d)解除引用相同的内存区域会打印出两个不同的答案(虽然如果我在问题1中所想的原因可以解释这一点)。
能否有人对此进行解释,将不胜感激。同时,如果有人能够解释其原因,那就更好了。

Malloc可能会将请求的大小向某个倍数四舍五入。不知道*nix,但Windows喜欢将其四舍五入到8字节的倍数。如果是这种情况,您正在写入您请求之外的区域,但它恰好在安全边界内,因此您不会破坏其他任何东西。 - DCoder
你小心地滥用了强制类型转换的能力,以获得未定义的行为。C语言赋予你强大的能力和灵活性去做一些“坏事”。你必须承担起责任来不去做这些事情 - dmckee --- ex-moderator kitten
@dmckee 我知道我正在做的事情不应该这样做,但在偶然发现这个问题后,我很好奇想知道背后的确切原因。我最初猜测的是,将自动类型转换为 int* 的 2 字节分配的 void* 指针会导致 int* 指针访问更多的内存,因为在我的机器上 int 是 4 字节。这个猜测正确吗? - abhi
1
当然,接下来真正有趣的部分就开始了。阅读关于大端和小端的内容。然后阅读关于混合端的内容,并准备好感到不适。 - dmckee --- ex-moderator kitten
3个回答

2

首先回答你的第二个问题:

这是因为一个int通常有4个字节,最高位字节可能存储在前两个位置。而一个只有2个字节的short也会将其数据存储在前两个位置。显然,将65536存储在一个int和一个short中,但指向相同的内存位置,将导致数据在intshort之间偏移了两个字节,其中int的两个最低有效字节对应于short的存储。

因此,当编译器打印*d时,它将其解释为一个short并查看对应于short存储的区域,这不是编译器之前写入*p时存储65536的地方。请注意,写入*p = 65536;覆盖了以前的*d = 65536;,用0填充了两个最低有效字节。

关于第一个问题:编译器并没有将*p65536存储在2个字节内。它只是超出了你分配的内存范围,这可能会在某个时候导致错误。


因此,自动类型转换为2字节分配的void指针的int会访问比2字节更多的内存,因为在我的机器上int是4字节。我的假设正确吗? - abhi
感谢您清晰明了的回答,解释得非常详细 :) - abhi
1
从概念上来说,就是这样。从技术角度来看,也许在你的句子中说“自动类型转换”并不是最好的措辞,因为实际上没有进行任何转换 - 只是编译器按照一种数据类型的方式访问内存中的字节,而不是按照另一种数据类型的方式访问它们。 - Dan Nissenbaum
哦,我的错。再次感谢您的回复和详细解释 :) - abhi

1
在 C 语言中,没有任何保护措施防止写入分配范围之外的内存。千万不要这样做,可能会导致意想不到的后果。这里它看起来能够正常工作,是因为你分配的两个字节后面恰好没有被用来做别的事情。

或者至少他没有做出任何表明该内存有其他用途的行为。对这种错误的沉默响应是让它们悄悄接近你的原因。 - dmckee --- ex-moderator kitten

0

1) 操作系统内存管理器的粒度为4K。一个比特位的覆盖不太可能引发AV/segfault,但它会破坏相邻位置的任何数据,导致:

2) 未定义行为。此行为集包括“显然正确的操作”(目前为止!)。


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