为什么printf需要强制转换?
要打印一些类型off_t
,建议使用以下代码:
off_t a; printf("%llu\n", (unsigned long long)a);
- 为什么格式字符串不够用?
- 如果没有铸造会有什么问题?
格式字符串不会告诉编译器对unsigned long long
执行强制转换,它只是告诉printf
它将接收一个unsigned long long
。 如果你传入的东西不是 unsigned long long
( off_t
可能不是),那么printf
会简单地误解它,结果令人惊讶。
原因是编译器不必知道有关格式字符串的任何信息。 如果你编写printf("%d", 3.0)
,一个好的编译器会给你一个警告信息,但是如果你编写printf(s, 3.0)
,编译器可以做什么, s
是在运行时动态确定的字符串?
编辑补充:正如Keith Thompson在下面的评论中指出的那样,编译器可以在很多地方执行这种隐式转换。 printf
是相当特殊的,在一个它不能的情况下。 但是如果你声明一个函数接受unsigned long long
,那么编译器将执行转换:
#include #include int print_llu(unsigned long long ull) { return printf("%llu\n", ull); // OK; already converted } int main() { off_t a; printf("%llu\n", a); // WRONG! Undefined behavior! printf("%llu\n", (unsigned long long) a); // OK; explicit conversion print_llu((unsigned long long) a); // OK; explicit conversion print_llu(a); // OK; implicit conversion return 0; }
原因是printf
被声明为int printf(const char *format, ...)
,其中...
是一个“可变参数”或“变量参数”符号,告诉编译器它可以接受任何数字和format
后的参数类型。 (显然printf
不能真正接受任何数量和类型的参数:它只能接受你告诉它的数字和类型,使用format
。但编译器对此没有任何了解;它留给程序员来处理它。)
即使使用...
,编译器也会进行一些隐式转换,例如将char
提升为int
,将float
提升为double
。 但是这些转换并不是特定于printf
,并且它们不依赖于格式字符串,也不能依赖格式字符串。
问题是你不知道off_t有多大。 它可以是64位类型或32位类型(或者可能是其他类型)。 如果你使用%llu,并且不传递(unsigned)long long类型,你将得到未定义的行为,实际上它可能只是打印垃圾。
不知道它有多大,最简单的方法是将它转换为系统支持的最合理的类型,例如无符号长条。 使用%llu的方式是安全的,因为printf会因为强制转换而收到unsigned long long类型。
(例如,在linux上,32位机器上off_t的大小默认为32位,如果在包含相关系统头之前通过#define _FILE_OFFSET_BITS=64
启用大文件支持, #define _FILE_OFFSET_BITS=64
64位)
printf
的签名如下所示:
int printf(const char *format, ...);
vararg ...
表示任何事情都可以遵循,并且根据C的规则,只要包含格式字符串,就可以将任何内容传递给printf
。 C根本没有任何构造来描述传递的对象类型的任何限制。 这就是为什么必须使用强制转换,以便传递的对象具有所需的类型。
这是C的典型特征,它在刚性和信任程序员之间划了界限。 一个不相关的例子是你可以使用char *
(不带const
)来引用字符串文字,但如果你修改它们,你的程序可能会崩溃。