将sizeof()的结果分配给ssize_t

时间:2019-03-22 15:04:14

标签: c posix sizeof

我碰巧需要将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_tsize_t之间的任何关系。

相关问题:

What type should be used to loop through an array?

4 个答案:

答案 0 :(得分:4)

不能保证SSIZE_MAX >= SIZE_MAX。实际上,情况极不可能,因为size_tssize_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通常不需要接口来保证处理所有数据。例如,readwrite都接受描述要读取/写入的缓冲区长度的size_t,并返回描述实际读取的字节数的ssize_t /书面;这意味着即使有更多数据可用,也不会读取/写入不超过SSIZE_MAX个字节。但是,Posix原理还指出,特定的实现可能会提供一个扩展,该扩展允许处理更大的块(“如果扩展提供了扩展范围,则使用扩展的兼容应用程序将能够使用整个范围”),该实现可以例如指定将-1以外的返回值强制转换为size_t来解释。这样的扩展将无法移植;在实践中,大多数实现方式确实将一次调用中可以处理的字节数限制为ssize_t中可以报告的字节数。

  • ptrdiff_t是(在标准C中)两个指针之间的差值结果的类型。为了正确定义指针的减法,两个指针必须指向同一对象,方法是指向该对象或指向紧随该对象之后的字节。 C委员会认识到,如果ptrdiff_tsize_t的等效符号,则可能无法表示两个指针之间的差异,从而导致行为不确定,但他们宁愿要求{ {1}}的类型比ptrdiff_t大。您可以对这个决定提出异议-很多人都做了-但自C90以来它就一直存在,现在看来改变的可能性不大。 (来自§6.5.6/ 9的当前标准用语:“如果结果无法在[size_t类型的对象中表示,则行为是不确定的。”)

    与Posix一样,C标准也没有定义未定义的行为,因此将禁止解释为非常大的对象中的两个指针相减是错误的。始终允许实现定义标准未定义的行为结果,因此对于实现来说,指定ptrdiff_tP是指向同一对象的两个指针是完全有效的,其中{ {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()等类似。