内存分配问题

13

这个问题是在一次工作面试的笔试环节中提出的:

 #include<alloc.h>
 #define MAXROW 3
 #define MAXCOL 4

 main()
  {
    int (*p)[MAXCOL];
     p = (int (*)[MAXCOL]) malloc(MAXROW*(sizeof(*p)));
  }

这个进程分配了多少字节?

说实话,我没有回答这个问题。我不理解对 p 的赋值。

有人可以解释一下答案是什么以及如何推导出来吗?


1
谁问这个面试问题显然从未尝试过编译这段代码。 - Charles Salvia
你确定吗?看起来可能会在某些垃圾系统上编译... - R.. GitHub STOP HELPING ICE
不加额外的 ) 是不行的。 - Charles Salvia
这144字节不是由于分配舍入而产生的,而是由于我在尝试测试时错误地将程序输入了一个额外的MAXROW乘法。纠正后,在具有4字节整数的系统上进行实验得到的答案为48字节,与给定的理论答案相匹配。对于造成的困惑,我很抱歉。 - Chris Stratton
10个回答

8

这是与平台相关的。

int (*p)[MAXCOL];声明了一个指向大小为MAXCOL(在本例中,MAXCOL为4)的整数数组的指针。因此,此指针的一个元素在目标平台上为4*sizeof(int)

malloc语句分配了一个类型为P的内存缓冲区大小的MAXROW倍。因此,总共分配了MAXROW*MAXCOL个整数。实际字节数将取决于目标平台。

此外,C运行时可能还使用了其他内存(作为malloc中的内部簿记以及在调用main之前发生的各种进程初始化位等),这也完全取决于平台。


7

p是指向一个由int类型的MAXCOL个元素组成的数组的指针,因此sizeof *p(括号是多余的)是这样一个数组的大小,即MAXCOL*sizeof(int)

malloc返回值上的强制类型转换是不必要、丑陋和被认为是有害的。在这种情况下,它隐藏了一个严重的错误:由于缺少原型,malloc被隐式地假定返回int,而这与其正确的返回类型void *不兼容,从而导致未定义行为。


如果您在回答中添加实际分配的字节,则这将是迄今为止最好的答案 - SiegeX
这个论点听起来不错,但实际情况并非如此。我对代码进行了仪器化处理,并发现分配了144个字节。 - Chris Stratton
2
@Chris:使用比预期更多的实现并不会使 R 的计算无效。您只能计算程序要求 malloc 分配多少内存 - 这是一个下限 - malloc 完全可以根据其内部使用的任何固定大小桶或页面大小来加速其算法而进行舍入。此外,程序可能会从其他初始化代码中分配堆 - 在比较 p 和显式请求的 malloc 的期望时,请注意不要包括它们。 - Tony Delroy
1
@Chris - R. 表示 MAXCOL*sizeof(int)*p 的大小,而不是分配的内存数量。 - David Gelhar
@Chris:我明白了。顺便说一下,R从未说过MAXCOL * sizeof(int)是正确的答案...他只是说这是sizeof *p的计算结果。在实际调用malloc时,这个值会乘以MAXROW。 - Tony Delroy
显示剩余5条评论

6

sizeof(*p) 的值为 MAXCOL*sizeof(int)。因此总共分配了 MAXROW*MAXCOL*sizeof(int) 字节。


6

您可能希望查看cdecl,以获取将C声明翻译为英语的帮助。在这种情况下,int (*p)[4]; 变成了 declare p as pointer to array 4 of int


+1 是因为我在回答中使用了 cdecl。 (最初我认为声明是函数指针数组,而不是原始整数) - Billy ONeal

5
#include<alloc.h>
#define MAXROW 3 
#define MAXCOL 4
main()   {
    int (*p)[MAXCOL];
    p = (int (*)[MAXCOL]) malloc(MAXROW*(sizeof(*p));
}

这个过程分配了多少字节? p是一个指针,在栈上会占用sizeof(int(*)[MAXCOL])的空间,虽然看起来比较吓人,但实际上几乎总是与sizeof(void*)sizeof(int*)或其他指针大小相同。显然,指针的大小决定了应用程序的位数分类,因此该指针的大小与之对应。
然后,p被指向从malloc获取的一些内存...
malloc( MAXROW * sizeof(*p) )

sizeof(*p)是指向pint数组的大小,即sizeof(int) * MAXCOL,因此我们得到

malloc( MAXROW * (sizeof(int) * MAXCOL) )

从堆中请求。为了举例说明,如果我们假设常见的32位int大小,那么我们需要48个字节。实际使用可能会被舍入到堆例程感觉合适的任何大小(堆例程通常使用固定大小的“桶”来加速操作)。

要确认这种期望,请将日志函数替换为malloc()

#include <stdio.h>

#define MAXROW 3
#define MAXCOL 4

void* our_malloc(size_t n)
{
    printf("malloc(%ld)\n", n);
    return 0;
}

int main()
{
    int (*p)[MAXCOL];
    p = (int (*)[MAXCOL]) our_malloc(MAXROW*(sizeof(*p)));
}

我的 Linux 电脑上的输出结果:

malloc(48)

将malloc返回的指针强制转换为p的类型并不影响内存分配量。

正如R所观察到的那样,如果没有malloc原型,编译器会期望malloc返回int而不是实际返回的void*。在实践中,最可能的是指针中最低的sizeof(int)个字节会在转换后保留下来,如果sizeof(void*)恰好等于sizeof(int),或者堆内存恰好从一个可表示为int的地址开始(即所有被截断的位都是0),那么稍后对指针的解引用可能会起作用。便宜广告:C++除非看到原型,否则无法编译。

话虽如此,也许您的alloc.h包含了malloc的原型...我没有alloc.h,所以我猜它不是标准的。

任何程序还将为许多其他东西分配内存,例如提供一些上下文的堆栈帧,其中可以调用main()。该内存的数量因编译器、版本、编译器标志、操作系统等而异。


3

我非常不喜欢这样的问题,因为我认为作为一名工程师,运行实验比假设自己知道在做什么要好得多 - 尤其是如果有怀疑的理由,例如程序不能按预期工作或有人制造了诡计性问题。

#include <stdlib.h>
#include <stdio.h>
#define MAXROW 3
#define MAXCOL 4

main()
{
  int (*p)[MAXCOL];
  int bytes = MAXROW * (sizeof(*p));
  p = (int (*)[MAXCOL]) malloc(bytes);
  printf("malloc called for %d bytes\n", bytes);
}

在32位Linux系统上: gcc test.c ./a.out malloc为48个字节调用

我很惊讶GCC没有抱怨它不是int main() - Billy ONeal
1
那种方法的问题在于它不能区分特定平台上实现的结果与语言标准要求所有实现执行的结果(12*sizeof int)。如果你仅仅依赖经验测试,而不是根据语言要求进行推理,你可能没有注意到自己将MAXROW乘以两次,最终得出了错误的答案。 - David Gelhar
在代码中找到拼写错误比发现对语言标准的假设失败要容易得多。我非常清楚我的系统上数据类型的大小,并且基本上指出了这一点。起初我不确定基本大小是int还是指针(因为两者大小相同),所以暂时将类型更改为short以查看会发生什么。经验性实验结果证明,测试假设是一个相当好的方法。 - Chris Stratton
1
当我年轻的时候,我可能会在面试/测试中尝试回答这个问题。现在我会告诉那些咄咄逼人的聪明面试官去死。在面试中有更重要的事情需要了解。 - Ciaran Keating

3

int (*p)[MAXCOL] == int (*p)[4] == "指向整型数组4的指针"(见下面的注释)

sizeof(*p) 等于 p 指向的内容大小,即 4 * sizeof(int)。将其乘以 MAXROW,得到最终答案:

12 * sizeof(int)

注意: 这与以下的语法形式不同:

int *p[MAXCOL] == int *p[4] == "指向整型的指针数组,包含4个元素"
括号的位置对于意义非常重要!


3
应该是MAXROW*MAXCOL*sizeof(int)字节的大小。

1
在codepad.org上运行以下代码:
//#include<alloc.h>
#define MAXROW 3
#define MAXCOL 4

int main()
{
    int (*p)[MAXCOL];
    p = (int (*)[MAXCOL]) malloc(MAXROW*(sizeof(*p)));

    int x = MAXROW*(sizeof(*p));
    printf("%d", x);

    return 0;
}

输出结果为48。

为什么?因为MAXROW是3,sizeof(*p)是16,所以我们得到3 * 16。

sizeof(*p)为什么是16?因为MAXCOL是4,所以p是指向一个由4个整数组成的数组的指针。每个整数是32位=4字节。数组中的4个整数* 4字节=16。

sizeof(*p)为什么不是4?因为它是指向的大小,而不是p的大小。要成为p的大小,它必须是sizeof(p),这将是4,因为p是一个指针。

严谨地说,您可以添加:

如果机器是64位的(比如说),答案应该是96。
由于问题中提到了“进程分配了多少字节?”,你需要加上指针p所占用的4个字节。
malloc函数可以分配比你请求的内存更多(但不会少),所以无法回答这个问题。
与第2点类似,你可以通过论述进程加载系统dll(例如C运行时dll)来运行,它也在为这些dll分配空间。然后你可以辩称被其他(非系统)进程注入到进程中的dll所分配的空间,比如Actual Window Manager等进程注入的dll。但我们要追求多么苛刻呢?
但我认为问题实际上是在问48,并且可能会额外给分解释96。

有些编译器即使在64位架构上也会将int设为32位宽。 - JeremyP
在C64上编译代码,int将会是8位宽。严谨地说... - ThatGirl

0

如果没有非标准的alloc.h,那么零字节怎么样?这甚至无法编译。如果无法编译,则无法运行,如果无法运行,则根本无法分配任何内存。


@mu:仅仅因为它是一个非标准头文件,并不意味着alloc.h无法被编译器访问到其他地方。我经常使用boost头文件,它们并不是标准的,但我的程序编译得很好。我相信这里也有很多人使用POSIX头文件,它们也不在标准库中,但同样可以正常工作。 - Billy ONeal
@Billy:真正的问题在于这个(面试)问题表达得非常糟糕,提出这个问题的人应该被打一巴掌。他们试图在语法上耍小聪明,却忽视了正确的main()和正确的引用。正如尼格尔·塔夫内尔所说:聪明和愚蠢之间只有一线之隔,而这个面试问题已经越过了那条线。 - mu is too short
@mu:当权威是Nigel时,很难对权威性上诉谬误提出异议跪拜 - Tony Delroy
@Billy:如果有人在面试问题中注意到了这个脑损伤的细节,我会感到印象深刻。但是,我曾经以可移植性为生,所以对这些事情有点偏见。也许那种聪明的口气有点过分了,但这不是面试,而是面试后“在酒吧里喝酒”的部分。也许你把这件事看得太严重了。 - mu is too short
1
@mu:我不会碰鼓... 不在那个乐队里。 - Tony Delroy
显示剩余4条评论

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