我尝试使用以下程序获取作为size_t
读取的字符数:
#include <stdio.h>
int main(void)
{
size_t i;
sscanf("abc", "%*s%zn", &i);
printf("%zu", i);
}
GCC 12对此发出警告:
scanf.c: In function ‘main’:
scanf.c:7:25: warning: format ‘%zn’ expects argument of type ‘signed size_t *’, but argument 3 has type ‘size_t *’ {aka ‘long unsigned int *’} [-Wformat=]
7 | sscanf("abc", "%*s%zn", &i);
| ~~^ ~~
| | |
| | size_t * {aka long unsigned int *}
| long int *
| %ln
这样做是正确的²;在C17标准草案第234页中,我们阅读了(我的重点)
不消耗任何输入。相应的参数应是指向有符号整数的指针,该整数将被写入该
fscanf
函数调用迄今为止从输入流读取的字符数。
早期标准包含类似的措辞。
那么,我如何(便携地)为这个转换创建一个size_t
的签名等价物呢?
在C++中,我可以使用std::make_signed_t<std::size_t>
,但这显然不是C代码的选项。如果没有这一点,%zn
转换在C.中似乎是不可用的
出现这种情况的真实世界案例来自于审查简单的光照发生器,我们想要更通用的strto𝑥()
形式,因此需要%n
来确定转换的结束。我知道我们可以在这里对所有的𝑥
使用普通的int
,但希望在报告为bug之前检查预期的行为。
²除了调用所需的类型signed size_t *
之外,这显然不是一个有效的C类型名称。
如何构造一个"带符号的size_t";用于
scanf("%zn")
?
你运气不好。自C17 起
z
指定后面的d、i、o、u、x、x或n转换说明符应用于类型指针指向size_t
或对应的带符号整数类型的参数。C17dr§7.21.6.2 11
n
不消耗任何输入。相应的参数应是指向带符号整数的指针。。。。C17dr§7.21.6.2 12
并且size_t
是一个无符号类型。
然而,C从未详细说明如何使对应的有符号整数类型。
标准C中没有指定与size_t
对应的签名类型。
替代方案:
-
先使用
"%n", &int_object
,然后使用size_t i = (unsigned) int_object;
。(铸造重要) -
使用
"%jn", &intmax_t_object
,然后使用size_t i = (size_t) intmax_t_object;
。
如果按下以键入signed_size_t
,则以下应可移植,但您可以自己操作。
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#if SIZE_MAX == UINT_MAX
typedef int signed_size_t;
#elif SIZE_MAX == ULONG_MAX
typedef long signed_size_t;
#elif SIZE_MAX == ULLONG_MAX
typedef long long signed_size_t;
#elif SIZE_MAX == UINTMAX_MAX;
typedef intmax_t signed_size_t;
#elif SIZE_MAX == USHRT_MAX
typedef short signed_size_t;
#else
#error Strange `size_t`.
#endif
_Static_assert(sizeof(size_t) == sizeof(signed_size_t), "Strange size_t");
关于非标准ssize_t
ssize_t
与对应的有符号整数类型不一定匹配。
即使是2018年第7期开放式集团基础规范也有:
ssize_t
这是size_t
的有符号模拟。措辞是这样的,实现可以选择使用更长的类型,也可以简单地使用size_t
的签名版本。。。
类似的问题如何使用";zd";带有printf()
?的说明符?。
"签名CCD_ 28";无论是在POSIX还是C标准的任何版本中,都不是真正作为标准定义的类型存在的。
POSIXssize_t
被指定为";ssize_t
型应能够存储至少在[-1, {SSIZE_MAX}]
范围内的值&";,因此即使ssize_t
可用;签署的CCD_ 33";价值
虽然没有一种类型可以保证足够大以容纳";有符号的CCD_ 34";严格一致性代码中的值类型,在我所知道的所有平台上,标准类型long long int
似乎是最";未来证明";。(size_t
大于unsigned long long int
的几率可能在零到无穷小之间,但我不认为任何版本的C标准都排除了这一点。)
在我写这篇文章的时候,据我所知,没有一个平台使用大于64位的size_t
值,而且long long int
保证至少是64位,所以它足够大。
int64_t
似乎是一个不错的选择,但如果系统的size_t
大于64位,int64_t
就会失败。我怀疑size_t
大于64位的任何系统实现都可能扩展long long int
以匹配。
你可以随时在代码中添加这样的内容来保护自己
#if SIZE_MAX > ULLONG_MAX
#error size_t larger than unsigned long long int
#endif
尽管使用" %lld ..."
格式说明符扫描long long int
比使用" " SCNd64 " ..."
宏扫描int64_t
更容易。。。
如果你真的想创建一个";匹配有符号的CCD_ 49";类型,您可以在#if
梯形图中扩展使用SIZE_MAX
,将其与各种U*MAX
值进行比较,以找到";最正确的";匹配要在CCD_ 53中使用的带符号整数类型。但是,您必须创建自己的scanf()
(可能还有printf()
宏)。
或者
或者,您可以在POSIX系统上使用ssize_t
,在非POSIX系统中创建自己的ssize_t
。IME没有CCD_ 58实现不事实上充当";签名CCD_ 59";价值