如何检测被赋为size_t的负数?

17

这个声明在g++ -pedantic -Wall(版本4.6.3)中可以顺利编译,且没有警告:

std::size_t foo = -42;

不太明显的错误是声明一个带有size_t参数的函数,并使用负值调用该函数。这样的函数能否保护不小心传入负参数(看起来像是遵守§4.7/2的大量五十亿)?
不完整的答案:
只是将size_t更改为(有符号)长整型会丢弃size_t的语义和其他优点。
将其更改为ssize_t仅仅是POSIX,而不是标准。
将其更改为ptrdiff_t是脆弱的,有时是错误的。
对于巨大值(高位设置等),进行测试是任意的。

相关问题:如果我给一个无符号变量赋负值会发生什么? - halex
2
+1 是因为这是一个好问题,而不是因为“umpteen quintillion”。 - Nicu Stiurca
测试高位比特不是任意的,而是标准。 - alecov
在我看来,unsigned 是邪恶的。我见过由于像 while(size_t index < container.size()-1) 这样的代码在空的 container 上触发而引起可怕的 bug。我知道当可用位数很少时,unsigned 类型有时可能有用,但是将被广泛使用的通用整数类型 size_t 定义为 unsigned 而不是 int,这是一个巨大的、可怕的错误。对不起,我有点啰嗦,这只是我的小毛病而已。unsigned ...呃。 - Michael
1
使用类似于这个的类。 - n. m.
显示剩余2条评论
2个回答

4
发出警告的问题在于,根据标准,这不是未定义行为。如果将有符号值转换为相同大小(或更大)的无符号类型,您可以稍后将其转换回原始有符号类型的有符号值,并在任何符合标准的编译器上获得原始值1
此外,使用已转换为size_t的负值用于各种错误条件是相当普遍的做法——许多系统调用返回无符号(size_t或off_t)值表示成功,返回已转换为无符号的-1表示错误。因此,将此类警告添加到编译器会导致现有代码的虚假警告。POSIX通过ssize_t试图对其进行编码,但这会破坏可能具有大于ssize_t的最大有符号值的返回值的调用。

1这里的“原始值”实际上意味着“一个比特模式,在与该有符号类型相比较时,该模式被视为等于原始比特模式”,填充位可能不会被保留,而且如果有符号表示具有冗余编码(例如,在符号-幅度表示中的-0和+0),它可能会被规范化。


运行时检测在这里比编译器警告更重要。但是你给了我一个想法,在函数内部将值强制转换回有符号数并测试<0。这很有效。所以我会把这个问题标记为已解决。 - Camille Goudeseune
@hvd:整个意图在于规范确实允许使用相同大小的有符号和无符号类型进行来回转换,并获得相同的值,即使它们的范围不同。 - Chris Dodd
1
@hvd:但是你正在从一个具有65535个值的类型转换为一个具有65536个值的类型,然后再转换回一个具有65535个值的类型。这里没有任何问题。 - Chris Dodd
@ChrisDodd [conv.integral] 表示:“如果目标类型是无符号的,则结果值是与源整数同余的最小无符号整数(模2^n,其中n是用于表示无符号类型的位数)。”,这要求(unsigned)-1的结果为UINT_MAX。然而,将其转换回int时,只有“如果目标类型是有符号的,并且它可以在目标类型(和位域宽度)中表示,则该值不变;否则,该值是实现定义的。”假设UINT_MAX > INT_MAX(int)UINT_MAX可能会给出任何值。 - user743382
@ChrisDodd 要明确的是,类似 unsigned u = -1; if (u == -2) abort(); 这样的语句是完全定义良好的。这是因为在比较时,u 没有被转换为 int,而是将 -2 转换为了 unsigned int - user743382
显示剩余2条评论

2
以下摘录来自私人图书馆。
#include <limits.h>

#if __STDC__ == 1 && __STDC_VERSION__ >= 199901L || \
    defined __GNUC__ || defined _MSC_VER
    /* Has long long. */
    #ifdef __GNUC__
        #define CORE_1ULL __extension__ 1ULL
    #else
        #define CORE_1ULL 1ULL
    #endif
    #define CORE_IS_POS(x) ((x) && ((x) & CORE_1ULL << (sizeof (x)*CHAR_BIT - 1)) == 0)
    #define CORE_IS_NEG(x) (((x) & CORE_1ULL << (sizeof (x)*CHAR_BIT - 1)) != 0)
#else
    #define CORE_IS_POS(x) ((x) && ((x) & 1UL << (sizeof (x)*CHAR_BIT - 1)) == 0)
    #define CORE_IS_NEG(x) (((x) & 1UL << (sizeof (x)*CHAR_BIT - 1)) != 0)
#endif

#define CORE_IS_ZPOS(x) (!(x) || CORE_IS_POS(x))
#define CORE_IS_ZNEG(x) (!(x) || CORE_IS_NEG(x))

这应该适用于所有无符号类型。


很好地使用了CHAR_BIT,https://dev59.com/3nA75IYBdhLWcg3wm6ax#3200969。但是隐式转换是如何起作用的呢?`(x) & ...将x强制转换为ULL吗?(x) &&表示(x != 0) &&吗?而ZPOS则表示>= 0`? - Camille Goudeseune
1
是的,IS_POS/IS_NEG测试 > 0< 0;而 IS_ZPOS/IS_ZNEG测试 >= 0<= 0。没有强制转换涉及;宏仅使用可用的最大无符号整数类型来构造位掩码表达式来测试最高位(即 (x) & ... 部分)。(x) && ... 部分的意思只是 (x) != 0 - alecov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接