如何解决编译器特定警告中的“unknown conversion type character 'z' in format”问题?

7
我正在处理跨编译到多个目标体系结构的代码。
我查看了搜索"printf size_t unknown conversion type character"警告的Stack Overflow中少数 帖子, 但这些帖子似乎都与minGW有关,因此那些答案本质上是针对_WIN32进行ifdef,不适用于我的实例,即printf无法将"%zu"识别为size_t的格式说明符,但是使用mips交叉编译器。 是否存在现有的编译器标志(针对指定的交叉编译器),使得libc能够将"%zu"识别为size_t的格式说明符?
$ cat ./main.c
// main.c

#include <stdio.h>

int main( int argc, char* argv[] )
{
  size_t i = 42;
  printf( "%zu\n", i );
  return 0;
}

$ /path/to/mips_fp_le-gcc --version
2.95.3
$ 
$ file /path/to/libc.so.6
/path/to/libc.so.6: ELF 32-bit LSB pie executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 2.2.15, not stripped, too many notes (256)
$ 
$ /path/to/mips_fp_le-gcc -mips2 -O2 -EL -DEL -pipe -Wall -Wa,-non_shared -DCPU=SPARC -DLINUX -D_REENTRANT -DPROCESS_AUID -DTAGGING -fPIC -I. -I../../../root/include -I../include -I../../../common/include -I../../..
/root/include  -DDISABLE_CSL_BITE -DDISABLE_DNS_LOOKUP     -DOS=UNIX -DLINUX -DPOSIX_THREADS -D__USE_GNU -D_FORTIFY_SOURCE=2 -DHANDLE_CSL_DUPLICATES  -DOS=UNIX -DLINUX -DPOSIX_THREADS -D__USE_GNU -D_FORTIFY_SOURCE=2 -DHANDLE_CSL_DUPLICATES  -DOS=UNIX -DLINUX -DPOSIX_THREADS -D__USE_GNU -D_FORTIFY_SOURCE=2 -DHANDLE_C
SL_DUPLICATES  -DOS=UNIX -DLINUX -DPOSIX_THREADS -D__USE_GNU -D_FORTIFY_SOURCE=2 -DHANDLE_CSL_DUPLICATES -o ./main.o -c main.c 
main.c: In function `main':
main.c:6: warning: unknown conversion type character `z' in format
main.c:6: warning: too many arguments for format

如果粗体问题的直接答案是“否”,那么还有哪些可能的解决方案?我能想到的可能性是...
  1. register_printf_function()
  2. 在特定于目标的宏中包装格式说明符(类似于this minGW-specific post
...还有其他想法吗?我强烈希望不涉及特定于目标的预处理器代码的解决方案,因此上述两个解决方案并不理想。
我认为(但不确定)跨编译器版本已经过时了;已知/保证新版本的工具链是否具有将“%zu”识别为size_t的格式说明符的libc
更新:这个交叉编译器似乎无法识别“-std=c99”,将其添加到编译器标志中会生成错误“cc1:未知的C标准'c99'”。

1
如果你试图编写可移植的代码,而其他方法都失败了,你可以引入一个宏定义,使用 inttypes.h 中用于 stdint.h 类型的风格:#define PRIuz "lu"(或者你需要使用的任何其他内容替代 "zu"),然后使用 printf("%" PRIuz "\n", i);。当然,这需要更改代码,我相信你正在尝试避免这种情况 :( - ikegami
gcc 2.95.3于2001年发布。是的,它很老了。不过2.95是第一个支持c99的版本,所以我很惊讶那个选项会出错。 - Shawn
根据文档,你需要使用-fstd=c9x-fstd=gnu9x - Shawn
3个回答

4
我在一个大的代码库中工作,它是使用几个不同的编译器进行编译的,其中一些是旧版本的,不支持%z,因此我们只能采用类似于以下的方式:
printf("size = %d", (int)size);

当然,对于小尺寸而言这是一个简单易行的方式。如果尺寸很大,则有其他替代方案可供选择。

printf("size = %u", (unsigned)size);

或者

printf("size = %lu", (unsigned long)size);

(还有其他明显的可能性。)

3
您的gcc不支持将“z”作为长度修饰符。这与MIPS无关,MIPS并没有任何影响,而是版本2.95.3缺乏支持。
在1998年2月9日,Andreas Schwab提交了一个新字段zlen来扩展对Z长度修饰符的支持。在此之前,gcc扩展使用Z作为size_t的转换类型说明符(而不是长度修饰符)。这段代码在gcc 2.95.3中,因此它应该识别Z,但不识别z。
在2000年7月17日,Joseph Myers添加了对z的支持,“c-common.c (scan_char_table):允许在diouxXn格式上使用“z”长度修饰符”。尽管在时间上早于gcc 2.95.3,但这是在gcc 3分支上,并且直到gcc 3.0才发布。因此,您的古老编译器根本不支持它。
因此,您可以更改代码以使用仍受支持的Z。您还可以基于编译器版本定义宏。
#if __GNUC__ < 3
#define PZ "Z"
#else
#define PZ "z"
#endif  

然后在代码中使用类似于printf("The size is %"PZ"u\n", sizeof(int));这样的格式即可。虽然您仍然需要修改代码,但最终结果并没有区别。因为在预处理之后,格式字符串仍然是新编译器上的%zu或旧编译器上的%Zu。将size_t参数转换为其他类型的想法实际上会改变代码的结果,因为它们在某些情况下将被转换为更大/更小的类型,具体取决于size_t是什么以及您转换成什么。
另外,如果您可以构建自己的工具链,可以对gcc进行补丁以了解z。我认为在“c-common.c”中使用zlen的case语句中进行一行更改就可以做到这一点。 register_printf_function()是glibc的一部分,也是printf()代码所在的地方。它允许您在运行时使用新的格式扩展printf。无法在编译时使用它来更改编译器。并且我认为当使用register_printf_function()时,gcc无法知道是否已添加新格式,因此无法执行printf类型检查。

那么,我可以这样说吗,"%Zu"是向后兼容的(或者从你的角度来看是向前兼容的)?也就是说,int main(int argc, char* argv[]) { size_t a = 42; printf("%Zu\n"); return 0; }代码在旧的2.95.3编译器和新的编译器上都能被接受吗?实验结果显示答案似乎是肯定的。 - StoneThrow
Z长度修饰符在至少到gcc 9.2版本时仍然被gcc支持以生成警告。当代码实际运行时,glibc也支持它。然而,clang不支持它用于警告,并会生成“无效转换”printf警告。在clang中运行时它仍然可以工作,因为glibc仍然使用相同的printf代码。 - TrentP

1
我最近将MinGW集成到我们的CI流程中。我们也遇到了同样的警告,但我们的修复非常简单。
以下是我们的原始代码:
#if defined(__GNUC__) || defined(__clang__)
#define FOO(_fmt_argnum, _first_param_num) __attribute__((format (printf, _fmt_argnum, _first_param_num)))
#else
#define FOO(_fmt_num, _first_param_num)
#endif

修复很微妙。

#if defined(__clang__)
#define FOO(_fmt_argnum, _first_param_num)  __attribute__((format (printf, _fmt_argnum, _first_param_num)))
#elif defined(__GNUC__)
#define FOO(_fmt_argnum, _first_param_num)  __attribute__((format (gnu_printf, _fmt_argnum, _first_param_num)))
#else
#define FOO(_fmt_num, _first_param_num)
#endif

解释:

libstdc++ 在 os_defines.h 中始终定义 __USE_MINGW_ANSI_STDIO,因此 printf 函数始终是 gnu_printf。非 GNU printf 格式属性表示遗留的 MSVCRT,它仍然没有 C99 支持,除了 ll 指定符外,并且期望 MS long double 格式(与 double 相同)。专门检查 Clang 的必要性是因为 Clang 不支持 gnu_printf。

TLDR:

在使用 GCC 时,请使用 gnu_printf 而不是 printf

注意:检查的顺序很重要,因为 clang 将定义 __GNUC__


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