为什么printf需要强制转换?

要打印一些类型off_t ,建议使用以下代码:

 off_t a; printf("%llu\n", (unsigned long long)a); 
  • 为什么格式字符串不够用?
  • 如果没有铸造会有什么问题?

格式字符串不会告诉编译器对unsigned long long执行强制转换,它只是告诉printf它将接收一个unsigned long long 。 如果你传入的东西不是 unsigned long longoff_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 )来引用字符串文字,但如果你修改它们,你的程序可能会崩溃。