【问题标题】:Can I do arithmetic on void * pointers in C?我可以在 C 中对 void * 指针进行算术运算吗?
【发布时间】:2011-04-30 11:58:50
【问题描述】:

这是有效的

void *p = &X; /* some thing */
p += 12;

如果是这样,p 现在指向什么? 我有(第三方)代码可以做到这一点(并且可以干净地编译),我的猜测是 void * 被视为 char *。我信任的 K&R 对这个话题保持沉默

编辑:我的小测试应用程序在 gcc 4.1.1 上运行良好,并将 void * 视为 char *。但是 g++ barfs

我知道如何正确地做到这一点。我需要知道我是否必须清理这个代码库才能找到它完成的所有地方。

BTW gcc -pedantic 抛出警告

总结:

C 规范不明确。它表示在表示和用作函数参数方面 void* =char*。但它对指针算法保持沉默。

  • gcc (4) 允许它并将其视为 字符 *
  • g++ 拒绝它
  • gcc -pedantic 发出警告
  • vs2010 c 和 c++ 拒绝它

【问题讨论】:

标签: c void-pointers


【解决方案1】:

不,这是不合法的。 void* 不能任意递增。它需要首先转换为特定类型。

如果您想将它增加特定数量的字节,那么这就是我使用的解决方案。

p = ((char*)p) + 12;

char 类型很方便,因为它定义的大小为 1 字节。

编辑

有趣的是,它在 gcc 上运行并带有警告。我在 Visual Studio 2010 上测试并验证它无法编译。我对标准的有限理解会说 gcc 在这里出现错误。能否添加以下编译标志

-Wall -ansi -pedantic

【讨论】:

  • Visual Studio 默认将代码编译为 C++,这就是它抛出错误的原因。
  • -pedantic 抛出警告。编译为 c++ 会致命
  • @casablanca 我仍然对在 C 中增加 void* 是否合法感到困惑。它当然看起来不便携。
  • @JaredPar:我和你一样感到惊讶,但仍在等待对此的明确答复。
  • @JaredPar:不,我不这么认为。我实际上并没有检查增量运算符的文本,但加法运算符的文本肯定只讨论指向对象类型的指针,而 void 是一个不完整的类型。所以((void*)p) + 12; 违反了语言限制(无效程序)。因此,MSVC 拒绝它是正确的,而 GCC 赋予它实现定义的语义并在编译时发出警告是正确的。
【解决方案2】:

引用规范:

§6.5.6/2:对于加法,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针,而另一个应具有整数类型。 (递增相当于加1。)

根据以下摘录,指向 void 的指针不是指向对象类型的指针:

§6.2.5/1:[...] 类型 分为对象类型(完全描述对象的类型)、函数类型(类型 描述函数)和不完整类型(描述对象但缺少 确定其大小所需的信息)。

§6.2.5/19:void 类型包含一组空值;它是一个不完整的类型,不能 完成。

因此,对于指向 void 类型的指针,未定义指针算法。

【讨论】:

  • +1 指出 C 规范在这点上没有歧义。我要补充一点,因为 6.5.6/2 是“约束”部分的一部分,所以这不是未定义的行为,而是需要符合要求的编译器才能发出诊断 (5.1.1.3/1)。在gcc -pedantic 的情况下,只是一个警告,但仍然是一个诊断。
【解决方案3】:

这取决于编译器。允许它的人认为 sizeof(*(void *)) 为 1。

编辑:它仅用于 void 指针算术。在这种情况下,使用 sizeof(int) 或 0 的步长是没有意义的。使用它的人的共同期望是尽可能小的步长。

【讨论】:

  • 什么编译器允许这样做?这意味着 sizeof(void) == 1 ……不理智。
  • 可能是 1 的小值。
  • @Jason,感谢您在我坐在椅子上无助地大笑时浪费了我一天的时间 :)
  • 以后的读者:GCC considers sizeof void == 1
【解决方案4】:

你的猜测是正确的。

在标准 ISO C99 第 6.2.5 节第 26 段中,它声明 void 指针和字符指针将具有相同的表示和对齐要求(释义)。

【讨论】:

  • 它还在 §6.5.6 第 2 段中说,两个操作数都具有算术类型,或者一个操作数具有整数类型,而另一个操作数是指向对象类型的指针(指向 void 的指针不是指向对象类型的指针)。因此,对指向 void 的指针的算术是定义的。
  • @dreamlax:而 未定义 意味着编译器可以为所欲为,包括使用 1 的元素大小。
  • @dreamlax 是的,同意。我误解了脚注 39。在 6.3.2.3 第 1 段中,它说 void * 可以转换为任何东西而不受惩罚。鉴于它需要具有与 char * 相同的特性,编译器将其隐式转换为 char * 似乎是合乎逻辑的。也就是说,我认为以这种方式操纵 void * 是真的不好的做法。
  • @BenL“未定义”确实意味着,但这不是未定义的行为,它违反了语言限制(动机是限制是操作数的类型,而不是它们的值; 我们知道它是一个约束,因为它前面有一个大标题“约束”;-))。违反约束必须导致诊断。其中,-pedantic 确实如此,所以没有害处。无论如何,没有-pedantic 的 GCC 不是,也没有声称是一个标准确认 C 编译器。
【解决方案5】:

您可能想查看此问题的最高投票答案

Pointer arithmetic for void pointer in C

【讨论】:

    【解决方案6】:

    我不认为你可以,因为它不知道它的类型,因此无法寻找正确的字节数。

    首先将其转换为类型,即(int)

    【讨论】:

    • 如果你转换为int,那么就实际添加的字节数而言,+= 12 基本上被解释为+= sizeof(int)*12。这似乎不是预期的行为。
    • @JaredPar 如果该指针确实指向一个整数,它会起作用吗?
    • 再次取决于意图。如果打算将其向前移动 12 个元素,那么可以。如果意图是将其向前移动 12 个字节,那么不会。很难从问题中读取意图,但我的假设是 OP 不希望增加 12 个字节。
    • 通常将 (void *) 转换为另一种指针类型是有意义的,例如转换为 (int *) 或 (char *) 或 (struct Foo *),或转换为某些依赖于实现的整数类型,例如 (size_t),即使在后一种情况下,结果行为没有以任何方式标准化。
    猜你喜欢
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    相关资源
    最近更新 更多