【问题标题】:Incrementing a void pointer and operator precedence on C/C++在 C/C++ 上增加 void 指针和运算符优先级
【发布时间】:2021-08-18 17:53:47
【问题描述】:

使用指针构建和访问通用数据结构 元素的大小不固定的地方(因此不能 使用结构)。

有趣的一点在于递增void * 指针 被转换为 int * 以指向下一个 int。

第一个错误的方法是:

// I want to assign -1 to the two int elements the memory
// is pointing to
*(int *) next++ = -1;
*(int *) next = -1;

这是错误的,因为 next++ 确实增加了 1。

可能有不止一个错误混淆运算符 优先级并假设要完成增量...无论如何...

即使拆分操作也不会增加:

*(int *) next = -1;
(int *)next++;  // does increment by one, maybe ++ comes before casting?
*(int *) next = -1;

工作示例是:

*(int *) next = -1;
next = (int *)next + 1;  // this increments by 4 as wanted
*(int *) next = -1;

但这让我有点困惑,因为我看不出有什么不同 所以我想我缺少一些语法规则 也许你们中的一些人可以给我一个提示!

【问题讨论】:

  • 跳过强制转换,分配给 int* 变量开始,然后改为。
  • C 还是 C++?不同的语言。
  • 目前我正在使用 C
  • 我同意第一条评论。如果你知道变量类型是int *,那么更好的代码就是一有机会就转换成int *,而不是像这样到处写转换。 int *ptrNext = (int *)next; 并简单地使用 ptrNext
  • @piertoni - 前段时间我也做了学习部分,我为自己做出的结论(主要是从经济学的角度来看)是让代码比它必须的更愚蠢并使用括号指定表达式所需的优先级。因此,我之前的评论和道歉听起来太直接了,并不是故意的。

标签: c++ c pointers operator-precedence


【解决方案1】:

有趣的一点在于递增一个void * 指针,该指针被转换为int * 以指向下一个int。

您必须了解的第一件事是标准 C(或 C++)没有在指向 void 或其他不完整类型的指针上定义增量或其他算术。这与指针算术是根据指向对象的大小/类型定义的事实一致,如果这些对象的类型不完整(就指针算术操作数的类型而言),那么没有足够的信息来使用该指针执行算术。

一些编译器,显然包括你的编译器,实现了一个扩展处理指针算术类型void *,就好像指针有类型char *。有时这是一个人想要的,但有时不是。我通常建议避免对此类扩展的任何依赖,并确保启用任何必要的编译器选项,以便在编译时诊断出使用它。

第一个错误的方法是:

// I want to assign -1 to the two int elements the memory
// is pointing to
*(int *) next++ = -1;
*(int *) next = -1;

这是错误的,因为 next++ 确实增加了 1。

是的,后缀++ 的优先级高于强制转换运算符,因此*(int *) next++ = -1 被解释为*(int *)(next++) = -1。如果编译器接受了,那么它是凭借我上面描述的扩展,所以next 被递增以指向下一个char,而不是下一个int

即使拆分操作也不会增加:

*(int *) next = -1;
(int *)next++;  // does increment by one, maybe ++ comes before casting?
*(int *) next = -1;

是的,这里的运算符优先级问题与前面完全相同,但另外,(int *)next++ 中的转换是无用的,因为结果未被使用。

工作示例是:

*(int *) next = -1;
next = (int *)next + 1;  // this increments by 4 as wanted
*(int *) next = -1;

但这让我有点困惑,因为我看不出有什么不同

虽然后缀 ++ 运算符的优先级高于强制转换运算符,但加法运算符 (+) 的优先级较低。因此,(int *)next + 1 被解释为((int *)next) + 1。因为指针运算是根据指向的类型完成的,所以会产生一个指向下一个int 的指针。

那么,回到你这样做的目的:

使用指针构建和访问通用数据结构,其中 元素的大小不固定

如果这样的系统需要知道它正在使用的对象的大小,以便将它们存储在一个数组中,例如,它通常将该大小作为一个或多个所涉及的数据结构的属性来携带或作为需要它的函数的参数。想要执行您描述的那种增量的严格符合程序然后会通过转换为char * 来执行此操作,可能像这样:

void *next_item(void *current_item, size_t item_size) {
    return ((char *) current_item) + item_size;
}

虽然这里实际上没有必要,但括号明确了操作优先级,以避免任何混淆。

注意:以上内容是为 C 编写的。注意事项与 C++ 基本相同,但与 C 不同的是,C++ 需要显式转换才能在 void * 和其他指针类型之间进行转换。 (原始代码中没有这些意味着 C 是其真正的目标语言。)上面的示例展示了 C 的良好代码风格,但需要将返回值强制转换为 void * 才能在 C++ 中使用。

【讨论】:

  • 很好的解释,谢谢。我在 linux 机器上使用gcc,我不打算使用编译器扩展。
【解决方案2】:

您不能使用指向void 的指针执行算术运算。运算符++ 的优先级高于(type) 转换,因此++ 绑定得更紧密。所以:

(int *) next++;

相当于:

(int *) (next++);

并且演员的结果被丢弃。

((int *) next)++ 之类的东西(这就是您似乎希望获得优先权发生的方式)是行不通的。变量是一个左值,可以赋值。当你施放它时,它变成了一个rvalue,你不能分配给它,你也不能取它的地址。

由于您不能直接增加void *,因此您唯一的选择(将其保留为void *)是对其进行简单的= 赋值:

next = (int *) next + 1

或为了完全清楚而使用括号,但会降低阅读的便利性:

next = ((int *) next) + 1

最好的选择是使用int * 开头。如果您需要保持变量类型的灵活性,只需创建一个新变量:

int *next_int = next;

仅当您使用 C++ 时才添加演员表。 (它们是不同的语言,有不同的规则。)

【讨论】:

  • Nitpick: postfix ++ 比强制转换运算符具有更高的优先级,因此不会涉及关联性。所有后缀一元运算符具有最高优先级,然后所有前缀一元运算符(包括强制转换)具有下一层。
  • @JohnBollinger 好点,我的眼睛跳到了他们在优先表中看到的第一个 ++。我已经修好了。
【解决方案3】:

保持简单:

int* ip = some_void_pointer;
ip[0] = -1;
ip[1] = -1;

或者不太优雅:

int* ip = some_void_pointer;
*ip = -1;
ip++;
*ip = -1;

您的代码中的问题与运算符优先级有关。只需避免在同一个表达式中将 ++ 与其他运算符混合使用,因为这很容易出错并且可能导致许多常见错误。优先级问题、无序访问、求值依赖顺序等。

【讨论】:

  • 谢谢,简洁优雅!
【解决方案4】:

++ 运算符的优先级高于强制转换运算符,因此:

(int *)next++; 

解析为:

(int *)(next++);

添加括号无济于事:

((int *)next)++; 

因为++ 运算符需要一个左值,而强制转换运算符的结果不是左值。

虽然next = (int *)next + 1 有效,但处理此问题的最佳方法是将next 分配给int * 类型的变量并使用它。

int *nextInt = next;
*next++ = -1;
*next = -1;

【讨论】:

    【解决方案5】:

    虽然不完全适合您的情况,但我编写了一个小的帮助函数,可以避免递增指针时的一些陷阱:

    /// <summary>
    /// Pointer arithmetic on byte level on other object types. This shall be used if the offset is in bytes, but T is some other pointer type.
    /// </summary>
    template <typename T>
    T* AddBytes(T* inPtr, int offset)
    {
        return (T*)(((byte*)inPtr) + offset);
    }
    

    使用喜欢

       int value = *AddBytes((int*)startOfStruct, sizeof(int));
    

    这从结构中获取字节偏移量 4 处的整数。 (很好:也适用于作业的左侧)

    如果你稍微改变一下,你可以得到 sizeof(int) 的增量:

    int* AddByInts(void* inPtr, int dwordCounts)
    {
        return (int*)(((int*)inPtr) + offset);
    }
    

    不允许直接使用 ++ 运算符,但无论如何可以使代码更清晰。

    【讨论】:

    • 我猜这只是C++ 而不是C
    • 没错,该模板仅适用于 C++(问题已标记为两者),但最后一个也适用于 C。
    • "他从结构中获取字节偏移量 4 处的整数" 嗯?不,这段代码转换为一个 int 指针,然后在 4x4 = 16 字节上进行指针运算。这对我来说没有任何意义。除此之外,保持简单而不是不必要的复杂。这样可以减少错误。
    • @Lundin 不,它没有。由于实现转换为 byte*,因此偏移量始终以字节为单位。第一个参数的强制转换是为了确保 T 是 int 类型,这将是返回指针的类型。
    • 也许我误解了哪个版本是解决方案。无论如何,这是非常琐碎的东西,而不是我们需要为其发明包装函数的东西。
    猜你喜欢
    • 1970-01-01
    • 2019-06-06
    • 2013-05-26
    • 2014-01-24
    • 1970-01-01
    • 2021-03-29
    • 2013-07-31
    • 2015-04-17
    • 1970-01-01
    相关资源
    最近更新 更多