此程序没有实际意义,只是我在学习中的一些困惑。除了我提到的几个疑问,不知道代码还有什么错误
#include<stdio.h>
#include<malloc.h>void f(int * q)
{
*(q + 2) = 56;
}
int main(void)
{
int * p;
p = malloc(0); /*1. 此时系统为其分配了0个字节(不是相当于没有分配内存吗),为什么还会返回首字节地址
2. 此时malloc前没有强制类型转换说明,怎么知道p指向了几个字节,也就是下面的*(p+1)和
*(p+2)不会报错
*/
*p = 10;
*(p + 1) = 2;
*(p + 2) = 6;
printf("%#x\n", p);
printf("%d\n", *p);
printf("%d\n", *(p+1));//此行不是相当于系统没有为其分配内存吗,为什么还能存储数据
f(p);
printf("%d\n", *(p + 2));
return 0;
}
C++技术网会员解答:
您好,感谢您对C++技术网的支持与信任。
首先回答第二个问题:此时malloc前没有强制类型转换说明,怎么知道p指向了几个字节,也就是下面的*(p+1)和*(p+2)不会报错。
可以明确告诉你,下面的代码是有错误的:
int * p;
p = malloc(0);
malloc返回的是void*指针,void*指针和int*指针是不兼容的类型,无法完成隐式的类型转换。所以你说的这个问题,和你问的就是符合的,是代码错了,不是你想错了。
最复杂的问题就是第一个问题:此时系统为其分配了0个字节(不是相当于没有分配内存吗),为什么还会返回首字节地址?
要想弄清楚这个问题,最有效的办法就是查看malloc函数的代码了!不相信还可以查看malloc函数的代码?来试试吧。
在malloc函数所在的行打上断点,然后进入调试状态,当程序停在断点处时,此时按F11,这样程序执行会进入malloc函数内部代码,然后可以用F10运行,也可以按F11逐层跟踪进去。F10不会进入函数内部,而是将函数作为一个个体整体执行,而F11会进入函数体。我们要看malloc函数的代码,也就是需要进入函数体。进去之后,我们再看看参数在内部如何传递,内部如何实现的。
通过跟踪,我们可以发现这样一段代码:
if (!_BLOCK_TYPE_IS_VALID(nBlockUse))
{
_RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
}
blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;
RTCCALLBACK(_RTC_FuncCheckSet_hook,(0));
pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
if (pHead == NULL)
{
if (errno_tmp)
{
*errno_tmp = ENOMEM;
}
RTCCALLBACK(_RTC_FuncCheckSet_hook,(1));
}
else
{
/* commit allocation */
++_lRequestCurr;
if (fIgnore)
{
pHead->pBlockHeaderNext = NULL;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = NULL;
pHead->nLine = IGNORE_LINE;
pHead->nDataSize = nSize;
pHead->nBlockUse = _IGNORE_BLOCK;
pHead->lRequest = IGNORE_REQ;
}
else {
/* keep track of total amount of memory allocated */
if (SIZE_MAX - _lTotalAlloc > nSize)
{
_lTotalAlloc += nSize;
}
else
{
_lTotalAlloc = SIZE_MAX;
}
_lCurAlloc += nSize;
if (_lCurAlloc > _lMaxAlloc)
_lMaxAlloc = _lCurAlloc;
if (_pFirstBlock)
_pFirstBlock->pBlockHeaderPrev = pHead;
else
_pLastBlock = pHead;
pHead->pBlockHeaderNext = _pFirstBlock;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = (char *)szFileName;
pHead->nLine = nLine;
pHead->nDataSize = nSize;
pHead->nBlockUse = nBlockUse;
pHead->lRequest = lRequest;
/* link blocks together */
_pFirstBlock = pHead;
}
/* fill in gap before and after real block */
memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);
/* fill data with silly value (but non-zero) */
memset((void *)pbData(pHead), _bCleanLandFill, nSize);
RTCCALLBACK(_RTC_FuncCheckSet_hook,(1));
retval=(void *)pbData(pHead);
代码看起来很复杂,没有关系。第五行代码的nSize是我们传给malloc的值,也就是0。我们可以看到,代码用传入的值得到了一个块内存大小,即blockSize。然后用块内存大小从堆里分配了一块内存,即_heap_alloc_base(blockSize),返回的地址给了pHead。pHead也就是指向这块分配的堆内存的起始地址。然后直到最后,我们看到返回值retval是基于pHead的pbData。
以上是一个大概的处理流程。简单来说,malloc分配内存不会按照我们给定的字节数然后按照字节数来分配内存的,而是按照块为单位来分配内存。一来可以减少内存碎片,二来可以提高内存的分配效率。多出来的内存,还可以记录其他信息。在调试程序的时候,需要更多的内存调试信息。而Release版程序,虽然没有很多的调试信息,但是还有一些其他信息,反正每次分配按照块大小分配,指定的大小并不一定是整倍数。而且内存使用本身就需要一些记录信息,系统会根据这些信息得知内存使用情况。所以,分配内存并不是简单给你指定的字节数的内存。
所以,尽管你指定了0字节的内存,分配也是可以成功的。返回的地址是分配出来的这块内存的起始地址。这个内存块记录着分配的内存大小为0。虽然分配了少量的内存,但是你并不能用。这个内存不在你支配的范围内。已分配的内存只是额外的开销而已。
然后最后面对指针的操作,其实都是不合理的。既然指定了0字节,在逻辑意义上来讲,你并没有得到内存。只不过确实分配了一些,这些内存用于存储控制记录信息,如果你强行使用,当然能写数据。不过这样带来的破坏,就不好说了。这就和进程栈的内存一样,虽然你没有申请,但是栈的内存就属于你程序的,你是可以写,但是可能对其他数据造成破坏。堆中分配的这些内存,也是属于你的进程的。虽然它用于其他目的,如果你要强行读写,也是可以的,最多就是破坏了而已。自家的矛盾自家解决就行了,系统不管。但是不建议这样做。这点要明白。举个不恰当的例子,你要拿刀捅自己,别人也没有办法,但是不建议这么做。