使用malloc()/calloc()创建内存的误解

3
我的理解是,malloc()/calloc() 分配内存时创建的对象一旦被创建,其地址将保持不变。但我经常使用的一个函数用于创建字符串数组,一直以来都表现良好,最近让我对自己的理解产生了质疑,也就是说,仅仅调用 calloc/malloc 函数,对象的内存地址是可以被移动的(而且确实被移动了)。
下面是一个创建字符串数组的函数:char **:
char ** CreateArrayOfStrings(char **a, int numWords, int maxWordLen)
{
    int i;
    a = calloc(numWords, sizeof(char *));   //create array of pointers
    if(!a) return a;                        //required caller to check for NULL
    for(i=0;i<numWords;i++)
    {
        a[i] = calloc(maxWordLen + 1, 1);   //create memory for each string 
    }
    return a;
}  

在我的系统上(Win7,32位编译,ANSI C),这行代码是:
a = calloc(numWords, sizeof(char *));   //create array of pointers  

创建一个连续的内存块,大小为numWordschar *,在这种情况下为7,得到28个字节:

enter image description here

内存跨越从地址0x03260080 + 1C (0x0326009C)开始
或者:

a[0] is at 0x3200260080    
a[1] is at 0x3200260084    
a[2] is at 0x3200260088    
a[3] is at 0x320026008C
a[4] is at 0x3200260090    
a[5] is at 0x3200260094    
a[6] is at 0x3200260098    

然后,我为每个numWords(7)个字符串创建内存。

for(i=0;i<numWords;i++)
{
    a[i] = calloc(maxWordLen + 1, 1);   //maxWordLen == 5 in this example
}  

这导致以下结果: enter image description here
这表明指针 a[1] - a[6] 的内存位置已被更改。
有人能解释一下在 malloc()/calloc() 中为什么会发生这种情况吗?

1
为什么会显示指针已经改变了? - Hogan
1
@wildplasser - 这是一个更正还是解释? - ryyker
1
忽略wildplasser——他说的话毫无意义。我想知道你为什么认为内存已经改变了——你是在反对分配的内存不连续吗? - Hogan
1
@wildplasser - 错了,a 是从函数中返回的。 - Hogan
1
顺便提一下,你的术语可能会妨碍你的理解:内存并不是由malloc()“创建”的。你的内存已经存在,并且在free()之后仍然存在。Malloc()只是将其标记为属于你的间隔。 - Lee Daniel Crocker
显示剩余18条评论
3个回答

7

看来您正在将苹果与橙子进行比较:

  • 当您打印a[i] is at ...指针时,您显示了数组a内部元素的地址
  • 然而,当您显示内存布局时,您展示了这些地址的,它们本身就是指针,因此整个情况看起来很混乱。

如果在对a[i]进行calloc结果赋值之前打印其值,应该会得到全部为零的结果,因为calloc将内存清空为NULL。在赋值后,您将在每个a[i]处看到6字节块的指针,这是完全合理的。

总结一下,您对使用malloccalloc分配内存时发生的情况的初始理解是正确的:一旦分配了一块内存,它的地址*保持不变。

* 在具有虚拟内存的系统上,我应该说“其虚拟地址”。


我认为你所说的是正确的,实际上,当我查看那个28字节块的实际内存时,它被填充了零。但是我还可以在我的资源管理器中看到一个工具(可能我误解了),显示了我假设的7个指针(a[0] - a[6])的地址。此时,我尚未为这些位置分配任何值,只是分配了内存。我发布的带有红点的图像是否没有指示内存地址? - ryyker
4
由于您有一个指针数组,这里涉及到两个地址:(1)数组元素本身的地址,即&a[0]、&a[1]、...;(2)存储在该地址中的地址,即a[0]、a[1]、...。第一组地址永远不会改变,因为它们是从a计算出的偏移量,这与&a[0]相同。当您对a[i]赋值时,第二组地址会发生变化。 - Sergey Kalinichenko

3
那些地址的内存没有改变。你正在创建一个28字节大的空间(a),然后在每个元素上动态分配了第二个6字节的内存块,并且该内存块有自己的地址。
换句话说,在a[1] (内存地址0x03260084),存储的值是指向内存地址0x32600D0的指针。
要检查每个内存位置和其值,请尝试以下操作:
for ( i = 0; i < 8; i++ )
{
    printf("a[%d] %p %p\n",i,&(a[i]),a[i]);
}

谢谢,这是一个很好的测试。我认为它与@dasblinkenlight的评论结合起来将解决我的问题。 - ryyker

1
当你调用calloc(numWords, sizeof(char *))时,你请求操作系统在你的系统上分配numWords个大小为“4个字节”的指针,这就是为什么结果块是4 * numWords字节,并且它返回第一个指针的地址。现在你可以存储将保存实际数据的指针的地址了。第n次调用calloc(maxWordLen + 1, 1)将返回大小为maxWordLen的块的地址,并将其存储在由a[n]指向的内存位置中,这只是第一次调用calloc返回的地址加上n * sizeof(char *)

在查看您(和其他人)的评论时,“现在您可以存储将保存实际数据的指针的地址”我认为是我误解的核心。我仍在思考,但感谢您的答案。 - ryyker

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