【问题标题】:Void pointers pretending to be void double pointersvoid 指针伪装成 void 双指针
【发布时间】:2013-12-18 17:49:18
【问题描述】:

我一直在思考。我还没有找到任何直接回答这个问题的东西,但我想我知道答案;我只是想从一些更有经验的人那里得到一些意见。

已知:

一个空指针只指向一个内存地址。它不包含类型信息。

一个 int 指针指向一个包含 int 的内存地址。它将读取内存地址中指向整数的任何内容,而不管最初填充到地址中的内容。

问题:

如果一个 void 双指针 void ** foo 指向一个动态分配的 void 指针数组

void ** foo = malloc(sizeof(void *) * NUM_ELEMENTS);

这是真的吗,正如我所假设的那样,由于 void 指针的独特性质实际上缺乏任何类型的信息,而不是 void ** foo 一个等效的语句将是

void * bar = malloc(sizeof(void *) * NUM_ELEMENTS);

当我通过分配特定类型来使用间接访问时,例如 with

(有人指出我不能取消引用 void 指针。为了清楚问题的目的,下一行更改为适合该信息)

int ** fubar = bar;

我会从 single void 指针中得到一个适当的指针,它只是 acting 就像一个 double 指针?

还是这一切都在我的脑海中?

【问题讨论】:

  • 您不能取消引用 void 指针。我认为在没有编译器扩展的情况下你也不能用它来做指针运算。你需要先投射它。
  • 如何使用 int ** fubar = bar; ?
  • 我认为可能会起作用,直到您尝试取消引用它。如果您将指针强制转换为错误的类型并尝试取消引用它,C 将不会执行转换以尝试使操作有意义。你会得到未定义的行为。

标签: c pointers void-pointers indirection


【解决方案1】:

在 C 标准下,您给出的代码的行为是未定义的,因为您分配了一个 void 指针数组,然后尝试将其用作 int 指针数组。标准中没有任何内容要求这两种指针具有相同的大小或对齐方式。现在如果你说

void * bar = malloc(sizeof(int*) * NUM_ELEMENTS);
int ** fubar = bar;

那么一切都会好起来的。

现在在绝大多数机器上,int*void* 实际上具有相同的大小和对齐方式。所以你的代码在实践中应该可以正常工作。

另外,这两者是不等价的:

void ** foo = malloc(sizeof(void *) * NUM_ELEMENTS);
void * bar = malloc(sizeof(void *) * NUM_ELEMENTS);

这是因为foo 可以在任何元素处取消引用以获得空指针,而bar 不能。例如,这个程序是正确的,在我的 32 位机器上打印 00000000

#include <stdio.h>
#include <stdlib.h>

int main(void) 
{
  void **a = calloc(10, sizeof(void*));
  printf("%p\n", a[0]);
  return 0;
}

另外一点是,您似乎认为类型信息在机器级别的指针中是明确的。这是不正确的(至少对于绝大多数实现而言)。 C 指针的类型通常只在程序编译时表示。到编译完成时,显式类型信息通常会丢失,除非在调试符号表中,这些符号表不是可运行的代码。 (这也有一些小的例外。对于 C++,情况就大不相同了。)

【讨论】:

  • 我相信您对我的问题感到困惑。
【解决方案2】:

允许将malloc 的结果分配给void * 对象,然后再将其分配给int ** 对象。这是因为malloc 的返回值无论如何都具有void * 类型,并且保证适合将指针分配给具有基本对齐要求的任何类型的对象。

但是,这段代码:

#define NUM_ELEMENTS 1000
void *bar = malloc(sizeof(void *) * NUM_ELEMENTS);
int **fubar = bar;
*fubar = 0;

C 标准不保证工作;它可能有未定义的行为。原因并不明显。 C 标准不要求不同类型的指针具有相同的大小。 C 实现可以将int * 的大小设置为一百万字节,将void * 的大小设置为四个字节。在这种情况下,分配给 1000 个void * 的空间不足以容纳一个int *,因此分配给*fubar 的行为未定义。通常,人们会以这种方式实现 C,只是为了证明一个观点。但是,在较小的范围内也可能出现类似的错误:在 C 实现中,不同类型的指针具有不同的大小。

如果指针具有适合目标类型的对齐方式,则指向对象类型的指针可以转换为指向另一个对象类型的指针。如果是这样,则将其转换回来会产生一个具有原始值的指针。因此,您可以将指向void * 的指针转换为指向void 的指针并返回,并且您可以将指向void * 的指针转换为指向int * 的指针并返回,前提是对齐是合适的(如果指针由malloc 返回,并且您没有使用具有扩展对齐方式的自定义对象。

通常,您不能使用指向对象类型的指针写入,然后使用指向不同对象类型的指针读取相同的字节。这违反了别名规则。一个例外是,如果其中一个指针指向字符类型。此外,许多 C 实现确实支持这种别名,但可能需要设置命令行选项才能启用这种支持。

这种对别名的禁止包括重新解释指针。考虑这段代码:

int a;
int *b = &a;
void **c = (void **) &b;
void *d = *c;
int *e = (int *) d;

在第四行,c 指向b 占用的字节,但*c 试图将这些字节解释为void *。这不能保证有效,因此d 获得的值不一定是指向a 的指针,即使它如最后一行那样转换为int *

【讨论】:

  • 好的,接下来进一步阐述,void * foo = malloc(sizeof(int *) * NUM); 的后果是什么然后将其转换为 int ** bar = foo;并跟进 for(...) *(bar + i) = malloc(sizeof(int) * LEN);?
  • @JonahNelson:就 C 标准而言,这很好。 malloc 返回一个void *,所以对foo 的赋值只记住了那个值而不改变它。然后用foo 初始化int **bar 只需将bar 设置为该值,就像普通int **bar = malloc(…) 一样。
  • 所以实际上没有任何目的使用 void ** foo 或 void *** bar 或任何类似的东西,因为 void * fubar 可以做同样的工作只要它是正确的转换回正确的指针?
  • @JonahNelson:拥有不同类型的一个主要目的是显示您打算如何使用数据,以便编译器可以在您编译时检查您的使用并捕获错误,而不是在几年后部署应用程序时之前没有发现的错误会引发火灾。您应该malloc 的结果分配给您打算使用分配空间的类型的指针。
  • 大声笑...我明白了。至于分配 malloc 的结果,更多的是关于希望能够处理不同数据类型并只返回 void 指针本身的库的可移植性。
猜你喜欢
  • 2014-05-30
  • 2020-04-01
  • 2021-07-22
  • 2016-05-23
  • 1970-01-01
  • 1970-01-01
  • 2019-09-27
  • 2014-05-13
  • 1970-01-01
相关资源
最近更新 更多