当前位置:C++技术网 > 资讯 > c语言中关于指针和malloc的几个问题

c语言中关于指针和malloc的几个问题

更新时间:2017-05-30 19:23:41浏览次数:1+次

此程序没有实际意义,只是我在学习中的一些困惑。除了我提到的几个疑问,不知道代码还有什么错误



#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字节,在逻辑意义上来讲,你并没有得到内存。只不过确实分配了一些,这些内存用于存储控制记录信息,如果你强行使用,当然能写数据。不过这样带来的破坏,就不好说了。这就和进程栈的内存一样,虽然你没有申请,但是栈的内存就属于你程序的,你是可以写,但是可能对其他数据造成破坏。堆中分配的这些内存,也是属于你的进程的。虽然它用于其他目的,如果你要强行读写,也是可以的,最多就是破坏了而已。自家的矛盾自家解决就行了,系统不管。但是不建议这样做。这点要明白。举个不恰当的例子,你要拿刀捅自己,别人也没有办法,但是不建议这么做。