我碰巧需要将sizeof(x)
与ssize_t
的结果进行比较。
当然,GCC给出了一个错误(幸运的是,我(我使用过-Wall -Wextra -Werror
),所以我决定做一个宏来拥有sizeof()
的签名版本。
#define ssizeof (ssize_t)sizeof
然后我可以像这样使用它:
for (ssize_t i = 0; i < ssizeof(x); i++)
问题是,我可以保证SSIZE_MAX >= SIZE_MAX
吗?我以为可悲的是,这永远不会成为现实。
或者至少是sizeof(ssize_t) == sizeof(size_t)
,它将削减一半的值,但仍然足够接近。
我在POSIX文档中没有发现ssize_t
和size_t
之间的任何关系。
相关问题:
答案 0 :(得分:4)
不能保证SSIZE_MAX >= SIZE_MAX
。实际上,情况极不可能,因为size_t
和ssize_t
可能是对应的无符号和有符号类型,因此(在所有实际体系结构上)SIZE_MAX > SSIZE_MAX
。将无符号值转换为无法保存该值的带符号类型是无符号行为。因此从技术上讲,您的宏存在问题。
实际上,至少在64位平台上,如果要转换为ssize_t
的值是实际存在的对象的大小,则不太可能遇到麻烦。但是,如果对象是理论对象(例如sizeof(char[3][1ULL<<62])
),您可能会感到不快。
请注意,类型ssize_t
的唯一有效负值是-1,这是一个错误指示。您可能会混淆Posix定义的ssize_t
和{C1以来在标准C中定义的ptrdiff_t
。这两种类型在大多数平台上都是相同的,并且通常是与size_t
对应的有符号整数类型,但是任何一种标准都不能保证这些行为。但是,这两种类型的语义是不同的,因此在使用它们时应注意这一点:
ssize_t
由多个Posix接口返回,以使该函数能够发信号通知已处理的字节数或错误指示。错误指示必须为-1。没想到ssize_t
会适合任何可能的大小; Posix的基本原理指出:
要限制符合标准的应用程序不能以大于
{SSIZE_MAX}
的片段形式执行I / O。
对于大多数返回ssize_t
的接口来说,这不是问题,因为Posix通常不需要接口来保证处理所有数据。例如,read
和write
都接受描述要读取/写入的缓冲区长度的size_t
,并返回描述实际读取的字节数的ssize_t
/书面;这意味着即使有更多数据可用,也不会读取/写入不超过SSIZE_MAX
个字节。但是,Posix原理还指出,特定的实现可能会提供一个扩展,该扩展允许处理更大的块(“如果扩展提供了扩展范围,则使用扩展的兼容应用程序将能够使用整个范围”),该实现可以例如指定将-1以外的返回值强制转换为size_t
来解释。这样的扩展将无法移植;在实践中,大多数实现方式确实将一次调用中可以处理的字节数限制为ssize_t
中可以报告的字节数。
ptrdiff_t
是(在标准C中)两个指针之间的差值结果的类型。为了正确定义指针的减法,两个指针必须指向同一对象,方法是指向该对象或指向紧随该对象之后的字节。 C委员会认识到,如果ptrdiff_t
是size_t
的等效符号,则可能无法表示两个指针之间的差异,从而导致行为不确定,但他们宁愿要求{ {1}}的类型比ptrdiff_t
大。您可以对这个决定提出异议-很多人都做了-但自C90以来它就一直存在,现在看来改变的可能性不大。 (来自§6.5.6/ 9的当前标准用语:“如果结果无法在[size_t
类型的对象中表示,则行为是不确定的。”)
与Posix一样,C标准也没有定义未定义的行为,因此将禁止解释为非常大的对象中的两个指针相减是错误的。始终允许实现定义标准未定义的行为结果,因此对于实现来说,指定ptrdiff_t
和P
是指向同一对象的两个指针是完全有效的,其中{ {1}},则Q
是指针之间的数学正确差,即使减法溢出也是如此。当然,依赖于这种扩展的代码不会完全可移植,但是如果扩展足够普遍,那可能就不成问题了。
最后一点,将-1用作错误指示(在P >= Q
中)和作为指针减法的可能可转换结果(在(size_t)(P - Q)
中)的歧义不太可能是如果ssize_t
与指针一样大,则在实践中出现。如果ptrdiff_t
与指针一样大,则size_t
的数学正确值可以是size_t
(又名P-Q
)的唯一方法是对象{{1 }}和(size_t)(-1)
所指的大小为SIZE_MAX
,假设P
与指针的宽度相同,则意味着该对象加上随后的字节占据了每个可能的指针值。这与某些指针值(Q
)必须与任何有效地址不同的要求相矛盾,因此我们可以得出结论,对象的真实最大大小必须小于SIZE_MAX
。
答案 1 :(得分:3)
请注意,您实际上无法做到这一点。
x86 Linux中最大可能的对象的大小略小于0xB0000000,而SSIZE_T_MAX
为0x7FFFFFFF。
我还没有检查read
和东西是否真的可以处理最大可能的对象,但是如果它们可以按以下方式工作:
ssize_t result = read(fd, buf, count);
if (result != -1) {
size_t offset = (size_t) result;
/* handle success */
} else {
/* handle failure */
}
您可能会发现libc
已被删除。如果是这样,那么在内核良好的情况下这将起作用:
ssize_t result = sys_read(fd, buf, count);
if (result >= 0 || result < -256) {
size_t offset = (size_t) result;
/* handle success */
} else {
errno = (int)-result;
/* handle failure */
}
答案 2 :(得分:2)
我将把这个作为X-Y问题。您遇到的问题是,您想将带符号的数字与无符号的数字进行比较。您应该检查sizeof
的值是否小于零,而不是将ssize_t
的结果强制转换为ssize_t
。如果是,那么您知道它小于您的size_t
值。如果不是,则可以将其强制转换为size_t
,然后进行比较。
例如,这是一个比较函数,如果有符号数小于无符号数,则返回-1
;如果等于,则返回0;如果有符号数大于无符号数,则返回1:
int compare(ssize_t signed_number, size_t unsigned_number) {
int ret;
if (signed_number < 0 || (size_t) signed_number < unsigned_number) {
ret = -1;
}
else {
ret = (size_t) signed_number > unsigned_number;
}
return ret;
}
如果您想要的只是<
操作的等效物,则可以使用以下类似的代码来简化操作:
(signed_number < 0 || (size_t) signed_number < unsigned_number))
如果1
小于signed_number
,则该行将为您提供unsigned_number
,并且没有分支开销。只需执行一次额外的<
操作和一个logical-OR
。
答案 3 :(得分:1)
ssize_t是POSIX类型,未定义为C标准的一部分。 POSIX定义ssize_t必须能够处理区间[-1,SSIZE_MAX]中的数字,因此原则上它甚至不需要是常规的带符号类型。这个定义有点怪异的原因是,唯一使用ssize_t的地方是作为read / write / etc的返回值。功能。
实际上,它始终是与size_t大小相同的普通带符号类型。但是,如果您想真正了解自己的类型,除了处理IO syscall的返回值外,不要将其用于其他目的。对于一般的“指针大小”的有符号整数类型,C89定义了ptrdiff_t。实际上,它与ssize_t相同。
此外,如果您查看official spec for read(),您会看到对于'nbyte'参数,它说:'如果nbyte的值大于{SSIZE_MAX},则结果是实现定义的。 '。因此,即使size_t能够表示比SSIZE_MAX更大的值,使用定义的行为也要使用比IO系统调用更大的值(如上所述,唯一使用ssize_t的地方)。和write()等类似。