【问题标题】:What should happen to the negation of a size_t (i.e. `-sizeof(struct foo)`))?否定 size_t(即 `-sizeof(struct foo)`))会发生什么?
【发布时间】:2009-08-12 22:10:05
【问题描述】:

我正在处理一些包含表单表达式的代码

-(sizeof(struct foo))

size_t 的否定,我不清楚 C 和 C++ 标准对编译器的要求是什么。具体来说,从这里和其他地方环顾四周,sizeof 返回一个 size_t 类型的无符号整数值。在否定无符号整数时,我找不到任何明确的指定行为参考。有没有,如果有,是什么?

编辑:好的,所以关于无符号类型的算术有一些很好的答案,但不清楚这实际上是否如此。当这否定时,它是对无符号整数进行操作,还是转换为有符号类型并对其进行处理?从标准中期望的行为“想象它是相似幅度的负数,然后对无符号值应用'溢出'规则”?

【问题讨论】:

  • 我想知道这种东西被带到这个世界的原因吗?
  • @eJames:可能不是,但这样做肯定有“公平”的理由,例如使用负值来表示对幅度的不同解释。在有人指责我过早优化之前,这 a) 不是我写的代码; b) 是并行编程环境运行时的一部分,其中一个应用程序占 NSF 超级计算机上约 20% 的时间。换句话说,每个周期都很重要。
  • 它是在做否定还是减法的右边?整个语句可能会有用。
  • 这是一个否定。我会说清楚的。
  • 但是在 C 语言中仅仅做一个否定并没有做任何事情——它可能会被优化掉。否定的实际用途是什么?您确定可以发布使用它的完整声明吗?

标签: c++ c sizeof unsigned size-t


【解决方案1】:

ISO C 和 ISO C++ 标准都保证无符号算术是模 2n - 即,对于任何上溢或下溢,它都会“回绕”。对于 ISO C++,这是 3.9.1[basic.fundamental]/4:

声明为unsigned 的无符号整数应遵守算术模2n 的定律,其中n 是该特定大小的值表示中的位数整数。41

...

41) 这意味着无符号算术不会溢出,因为结果不能由生成的无符号整数表示 type 以比得到的无符号整数可以表示的最大值大一的数字为模减少 输入。

对于 ISO C(99),它是 6.2.5/9:

涉及无符号操作数的计算永远不会溢出,因为无法由生成的无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。

这意味着结果保证与SIZE_MAX - (sizeof(struct foo)) + 1相同。


在 ISO 14882:2003 5.3.1.7 中:

[...] 无符号的负数 数量是通过减去来计算的 它的值来自 2n,其中 n 是中的位数 提升的操作数。的类型 结果是提升的类型 操作数。

【讨论】:

  • 在 ISO 14882:2003 5.3.1.7 中添加:“[...] 无符号量的负数是通过从 2n 中减去其值来计算的,其中 n 是提升操作数中的位数。结果的类型是提升操作数的类型。"
  • 后一部分是与我提出的问题最相关的部分,尽管前一部分显示了它与标准中其他地方采用的方法的一致性。
  • 这不是必然 100% 可移植正确的。请参阅my answer 了解可能不是的异常情况。
【解决方案2】:

http://msdn.microsoft.com/en-us/library/wxxx8d2t%28VS.80%29.aspx

无符号数量的一元否定 通过减去值来执行 来自 2n 的操作数,其中 n 是 对象中的位数 给定无符号类型。 (微软 C++ 在处理器上运行,利用 补码算法。在其他 处理器,求反算法 可以不同。)

换句话说,确切的行为将是特定于架构的。如果我是你,我会避免使用这种奇怪的结构。

【讨论】:

  • 微软就是这样做的,还是他们这样做是因为它是实现定义的?
  • 我在搜索时确实找到了这个页面,并希望它能说明它所依赖的标准的哪一部分。它表明这不是严格定义的,但它是“未定义”、“实现定义”、未提及还是其他?另外,我认为您引用的文字略有错误。那应该从 2^n 中减去
  • 我无权访问 C 标准,但 GCC 也有这种行为(在 x86 上)。可悲的是,我无法访问使用非二进制补码表示来测试负数的系统。如果我没记错的话,整数表示是实现定义的,所以这也是。
  • ISO 14882:2003 5.3.1.7 基本上说了同样的话,但不包括关于非二进制补码机器的警告。
  • 确切的行为实际上是由 C 标准和 C++ 标准定义的。微软的评论给出了相同的结果,除非整数为 0(根据微软的 -0u 将是 2^32 如果 unsigned int 是 32 位,这是无稽之谈)。根据 C 和 C++ 标准,无符号 x 的 -x 计算数学值,然后反复加减 UINT_MAX + 1,直到结果在正确的范围内。
【解决方案3】:

取反无符号数对于在整个字中传播 lsb 以形成后续按位运算的掩码很有用。

【讨论】:

    【解决方案4】:

    我唯一能想到的就是错得让人头疼……

    size_t size_of_stuff = sizeof(stuff);
    
    if(I want to subtract the size)
        size_of_stuff = -sizeof(stuff);
    
    size_t total_size = size_of_stuff + other_sizes;
    

    溢出是一个特性!

    【讨论】:

    • 您的想象可能有点过于狭隘。假设将表达式的结果分配给 intlong - 一些有符号类型。
    【解决方案5】:

    来自current C++ draft standard,第 5.3.1 节第 8 句:

    一元- 运算符的操作数应具有算术或枚举类型,结果是其操作数的否定。对整数或枚举操作数执行整数提升。无符号量的负数是通过从 2n 中减去其值来计算的,其中 n 是提升的操作数中的位数。结果的类型是提升操作数的类型。

    所以结果表达式仍然是无符号的,并按照描述计算。

    用户@outis 在评论中提到了这一点,但我将把它放在答案中,因为 outis 没有。如果 outis 回来回答,我会接受。

    【讨论】:

    • 这与 pavel 发布的内容有何不同?
    【解决方案6】:

    size_t 是实现定义的无符号整数类型。

    否定size_t可能会为您提供size_t 类型的结果,具有通常的无符号模数行为。例如,假设 size_t 为 32 位且 sizeof(struct foo) == 4,则 -sizeof(struct foo) == 4294967292 或 232-4。

    除了一件事:一元 - 运算符应用 integer Promotions (C) 或 integral Promotions (C++)(它们本质上是相同的)到它的操作数。如果size_t 至少和int 一样宽,那么这个提升什么也不做,结果是size_t 类型。但是如果intsize_t 宽,所以INT_MAX >= SIZE_MAX,那么- 的操作数从size_t 被“提升”到int。在这种不太可能的情况下,-sizeof(struct foo) == -4

    如果您将该值分配回size_t 对象,那么它将被转换回size_t,从而产生您期望的SIZE_MAX-4 值。但是如果没有这样的转换,你会得到一些令人惊讶的结果。

    现在我从未听说过size_tint 更窄的实现,所以您不太可能遇到这种情况。但这里有一个测试用例,使用unsigned short 作为假设的窄size_t 类型的替代,它说明了潜在的问题:

    #include <iostream>
    int main() {
        typedef unsigned short tiny_size_t;
        struct foo { char data[4]; };
        tiny_size_t sizeof_foo = sizeof (foo);
        std::cout << "sizeof (foo) = " << sizeof (foo) << "\n";
        std::cout << "-sizeof (foo) = " << -sizeof (foo) << "\n";
        std::cout << "sizeof_foo = " << sizeof_foo << "\n";
        std::cout << "-sizeof_foo = " << -sizeof_foo << "\n";
    }
    

    我的系统(有 16 位 short、32 位 int 和 64 位 size_t)上的输出是:

    sizeof (foo) = 4
    -sizeof (foo) = 18446744073709551612
    sizeof_foo = 4
    -sizeof_foo = -4
    

    【讨论】:

    • 有一些嵌入式控制器,其中 RAM 被细分为不连续的块,每个块小于 256 字节,除非程序包含大于 ROM 中的对象 size_t 可能非常合理地是 8-位“unsigned char”(当然小于“int”)。我认为大多数这样的编译器实际上使 size_t 成为 unsigned int 因为期望它的行为像一个。太糟糕了,标准甚至没有描述此类事情的规范行为,因为让程序员向后弯腰以适应甚至可能不存在的实现是愚蠢的。
    • @supercat:标准确实描述了此类事物的规范行为:比int窄的无符号类型被提升为int。这不是你喜欢的行为。 (明确地说,这不是我更喜欢的行为。)
    • 标准可以很容易地指定size_t 排名低于int 的实现应被视为非规范;出于某种原因(例如与现有代码的兼容性)需要这样做的实现将被允许这样做,但强烈鼓励实现使size_t 至少与unsigned int 一样大,但没有令人信服的理由否则(例如与现有代码的兼容性)。我想不出任何技术原因为什么一个实现不能使size_t 至少和unsigned int 一样大;可以吗?
    • 除了有符号与无符号行为问题之外,还要考虑即使系统无法分配超过一定大小(例如 255 字节)的对象,它仍应检查请求大小以确保它们为零,如果不是则返回 null,而不是响应分配 8 个字节的 264 字节请求。
    猜你喜欢
    • 2013-09-01
    • 2015-09-16
    • 1970-01-01
    • 2021-12-16
    • 2017-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多