C语言中的size_t和ssize_t类型如何表示负数值?

14

size_t 声明为 unsigned int,因此它不能表示负值。
因此有 ssize_t,它是 size_t有符号 类型,对吗?
这是我的问题:

#include <stdio.h>
#include <sys/types.h>

int main(){
size_t a = -25;
ssize_t b = -30;
printf("%zu\n%zu\n", a, b);
return 0;
}

为什么我收到了以下错误信息:

18446744073709551591
18446744073709551586

作为结果?
我知道使用 size_t 可能是可行的,因为它是一个无符号类型,但为什么在使用 ssize_t 时我也得到了错误的结果?


5
你使用了错误的格式说明符。 - Mysticial
5
ssize_t 不是标准 C 的一部分,它来自 POSIX。在标准 C 中最接近 ssize_t 的类型是 ptrdiff_t - Jens Gustedt
1
size_t是一种无符号整数类型,但通常不是unsigned int。通常等同于unsigned longunsigned long long - Adrian McCarthy
4个回答

22
在第一种情况下,你正在给一个无符号类型赋值 - a。在第二种情况下,你正在使用错误的格式说明符。第二个说明符应该是%zd而不是%zu

2
ssize_t 是否定义为与 size_t 相同的大小?如果不是,"%zd" 不是可移植的答案。建议使用 printf("%jd\n", (intmax_t) b); - chux - Reinstate Monica
1
@chux %zd是为ssize_t指定的说明符。后续的整数转换对应于size_tssize_t参数。 - cnicutar
2
@chux 我引用了一份有关 printf 的 Linux man 手册。标准文档 也提到了它,只是没有那么清楚明确。 - cnicutar
2
@cnicutar 再次感谢:我在 Google 上搜索了“printf 的 Linux man 手册”并找到了它。请注意:提供的链接(Open Group Base Specification)上没有关于 ssize_t 的内容。 - chux - Reinstate Monica
2
@cnicutar 迟来的回复,但我并不认为“a signed integer type corresponding to a size_t argument”明确指代ssize_tssize_t的定义从未将其与size_t以任何方式联系起来。整个定义仅仅是:“用于字节计数或错误指示。”和“类型ssize_t应能够存储至少在范围[-1,{SSIZE_MAX}]内的值。”而SSIZE_MAX的定义是循环的。 - Andrew Henle
显示剩余4条评论

1
首先,您应该检查这两种类型的实际大小。以下代码片段类似于此:
#include <stdio.h>
#include <unistd.h>

int main() {
  printf( "sizeof(  size_t ) = %d bytes\n",(int) sizeof( size_t) );
  printf( "sizeof( ssize_t ) = %d bytes\n",(int) sizeof( ssize_t) );
  return 0;
}

我在两种情况下都得到了"8字节",这与long int和long long int相同,是最大的本机整数值。
当大小相同时(它们应该始终如此),size_t的"绝对值可以比ssize_t大2倍",而后者可以具有带符号(即正或负)值。
如果它们不同,则较大的一个将更大,并且因此可以容纳更大的值。
但最终,ssize_t和size_t是两种不同的类型,用于"谈论"大小、长度、内存量等等。
前者只是为了获得所需的信号来表示某种错误而放弃了1位的值。
最后,这两种类型并不可互换,至少不总是。 当大小可以超过2^63个字节/项时,差异是明显的。 size_t不会溢出,而ssize_t会。
在"正常"情况下,您可以从一种类型转换为另一种类型。 对于我之前提到的情况,您永远不应混合使用它们。
作为参考,strlen()malloc()都使用size_t,而read()readv()都使用ssize_t
因此,ssize_t 不是 size_t的有符号版本,因为在ssize_t中存在无法映射到size_t(如-1)的值,反之亦然。并且库函数使用其中一种类型。
然后,对于您的问题,您看到的两个数字相差5个单位,这正是您所期望的。您看到的是将这两个变量视为unsigned long时的值。尝试将它们作为signed long%ld)打印,以便仍然可以看到符号。

注意:Linux程序员手册PRINTF(3)中确实有“z:后面的整数转换对应于_size_t_或_ssize_t_参数”。 - chux - Reinstate Monica
尽管措辞有些不当,z是一个长度修饰符,而不是转换说明符。在我看来,根据它们的特定用途和含义,我会使用%ld%lu。但这也取决于个人口味。 - EnzoR
"它们具有不重叠的领域。" - 错误,它们重叠,数字0、1、2、3...在重叠部分中。 - M.M
啊啊啊!:-D当然。我的意思是函数要么使用size_t,要么使用ssize_t,从不同时使用。 - EnzoR

0
为什么我即使使用 ssize_t 也得到了错误的结果?请使用:
ssize_t b = -30;
printf("%jd\n", (intmax_t) b); 

使用匹配说明符,对于负的ssize_t不是%zu,也绝对不是"%zd"
如何在printf()中使用“zd”说明符?

ssize_t b = -30;
printf("%zu\n", b);  // problem.

ssize_t没有C指定的打印格式说明符。 C甚至没有指定ssize_t

C的各种扩展确实指定了ssize_t,在Linux的情况下,打印说明符也是如此。 {{link1:Linux程序员手册}}中有:

z:后面的整数转换对应于size_tssize_t参数",

printf("%zd\n", b);  // OK for that Linux

POSIX B.2.12 数据类型具有以下内容:

ssize_t这是size_t的带符号模拟。措辞是这样的,实现可以选择使用更长的类型或者只是使用size_t的底层类型的带符号版本。


由于 ssize_t 可能(不常见地)比 size_t 更宽,使用 "%zd" 可能会引发未定义行为(UB)。自 C99 以来,将其转换为最宽的标准有符号类型 intmax_t 并打印就足够简单了。

printf("%jd\n", (intmax_t) b);  // OK for general use

-4

由于 size_t 是无符号的,所以会发生溢出 当你试图将 size_t 设置为 (-val) 时, 你会得到溢出并得到 SIZE_T_MAX - val

例如: size_t val = -20; //val == 18446744073709551615 - 20;


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