【问题标题】:Memory allocated without allocation using malloc, how?使用malloc分配的内存没有分配,如何?
【发布时间】:2012-07-28 19:00:04
【问题描述】:

下面是一个简单的代码sn-p:

int main()
{
int *p;
p=(int*)malloc(sizeof(int));//allocate m/y 4 1 int 
printf("P=%p\tQ=%p",p,p+2);
}

在一个示例运行中,它给了我如下输出:

P=0x8210008 Q=0x8210010

P的起始地址是-P=0x8210008,下一个字节是0x8210009,下一个字节是0x821000A,下一个字节是0x821000B。所以int的4个字节到此结束。 我们没有使用 malloc 分配更多内存。 那么p+2是怎么把我们带到0x8210010的,也就是P(0x8210008)之后的8个字节。

【问题讨论】:

标签: c


【解决方案1】:

因为它把它当作指针的整数元素偏移量。您已经为单个整数分配了一个数组。当您要求p+2 时,它与&p[2] 相同。如果要从头开始两个字节,则需要先将其转换为char*

char *highWordAddr = (char*)p + 2;

【讨论】:

    【解决方案2】:

    首先,您打印了一个地址这一事实并不意味着在该地址分配了内存。您只是简单地添加了数字并产生了其他数字。

    其次,你通过加二得到的数字是比基地址大 8 而不是比基地址大 2,因为当你在 C 中将整数添加到指针时,算术是根据指向的-to 元素,而不是内存中的字节(除非指向的元素是字节)。假设您有一个 int 数组,例如 int x[8],并且您有一个指向 x[3] 的指针。向该指针添加两个会生成指向x[5] 的指针,而不是指向超出x[3] 开头的两个字节的指针。重要的是要记住 C 是一种抽象,而 C 标准指定了该抽象内部发生的情况。在 C 抽象内部,指针运算适用于元素数量,而不是原始内存地址。 C 实现(编译器和将 C 代码转换为程序执行的工具)需要对原始内存地址执行任何操作,以实现 C 标准指定的抽象。通常,这意味着编译器在将整数添加到指针时将其乘以元素的大小。所以 2 乘以 4(在 int 是 4 个字节的机器上),得到的 8 加到基地址上。

    第三,你不能依赖这种行为。 C 标准只为指向数组内对象的指针定义指针算法,包括数组末尾的一个虚构对象。此外,指向单个对象的指针就像一个元素的数组。因此,如果您有一个指向 int 的指针 p,则允许您计算 p+0p+1,因为它们指向数组中唯一的对象 (p+0) 和一个超出的虚构对象数组中的最后一个元素 (p+1)。您不允许计算p-1p+2,因为它们在数组之外。请注意,这不是取消引用指针(尝试在计算的地址读取或写入内存)的问题:即使仅计算地址也会导致 C 标准未定义的行为:您的程序可能会崩溃,它可能会给你“正确”的结果,或者它可能会删除你帐户中的所有文件,所有这些行为都符合 C 标准。

    仅仅计算越界地址不太可能产生这种奇怪的行为。但是,该标准允许这样做,因为某些计算机处理器具有不寻常的地址方案,需要比简单的算术更多的工作。可能仅次于平面地址空间的第二常见地址方案是基地址和偏移量方案。在这种方案中,四字节指针的高 16 位可能包含一个基地址,而低 16 位可能包含一个偏移量。对于给定的基地址 b 和偏移量 o,对应的虚拟地址可能是 4096*b+o。 (这样的方案只能寻址 220 个字节,并且 base 和 offset 的许多不同值可以引用同一个地址。例如 base 0 和 offset 4096 引用同一个地址作为 base 1 和偏移量 0。)使用基数和偏移量方案,编译器可以通过仅添加到偏移量并忽略基数来实现指针算术。 (这样的 C 实现最多只能支持 65536 字节的数组,范围仅可通过偏移量寻址。)在这样的实现中,如果您有指向 int 的指针 p,编码为 0x0000fffc(基数 0,偏移量 65532 ),而int 是四个字节,那么p+2 的值将是 0x00000004,而不是大八的值 (0x00010004)。

    这是一个示例,其中指针算术会产生您不会从平面地址机器中获得的值。很难想象根据 C 标准无效的指针算法会导致崩溃的实现。但是,考虑一个内存必须由进程手动交换的实现,因为处理器没有支持虚拟内存的硬件。在这样的实现中,指针可能包含内存中结构的地址,这些地址描述了磁盘位置和用于管理内存交换的其他信息。在这样的实现中,执行指针运算可能需要读取内存中的结构,因此执行无效指针运算可能会读取无效地址。

    【讨论】:

      【解决方案3】:

      C 很乐意让你做任何你喜欢的指针运算。仅仅因为p+2 看起来像任何其他地址并不意味着它是有效的。事实上,在这种情况下,它不是。

      当您看到指针算术不会超出分配的范围时,请务必小心。

      【讨论】:

      • 这无法回答提问者的问题,因为它没有解释为什么他们得到的值比基地址大 8 而不是大 2。首先,如果他们打印了 p+1,他们会得到一个大 4 的地址,因为向指针添加一个整数会产生该类型的下一个对象的地址,而不是内存中下一个字节的地址,因此 C 实现实现实现这一点所需的地址算术。其次,加 2 超出了编译器需要使指针算术工作的范围,因此行为是未定义的,如其他 cmets 所述。
      【解决方案4】:

      【讨论】:

      • 不,这叫做未定义行为。
      • 不,不会的。指针算法仅在数组中定义,直到最后一个数组元素“过去”的位置。由于所讨论的数组的长度为 1,因此添加 2 会超出这些范围,从而调用未定义的行为。
      • @paddy 不,这不好,“如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。”当然,这是一种在实践中不会导致意外的未定义行为(如果您足够小心,不要取消引用指针),但它仍然是 UB。
      • 确实,@paddy,标准说它是 UB,所以它是 UB。当然,我们正在分裂头发。你没注意到问题上的标签吗?
      • 这是未定义的行为,因为标准明确声明它是未定义的行为。来自 ISO/IEC 9899, Second edition, 1999-12-01, 6.5.6, 第 8 段,其中描述了将整数添加到指针(不取消引用结果,仅单独添加):“如果指针操作数和结果指向同一数组对象的元素,或数组对象的最后一个元素,求值不应产生溢出;否则,行为未定义。”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-23
      • 2012-12-26
      • 2014-03-28
      • 2023-04-01
      • 2012-02-26
      相关资源
      最近更新 更多