我认为标题不适合我的问题。 (我很感激,如果有人建议编辑)
我正在学习C"学习C艰难的方法。"。我使用printf
使用格式说明符输出值。这是我的代码段:
#include <stdio.h>
int main()
{
int x = 10;
float y = 4.5;
char c = 'c';
printf("x=%d\n", x);
printf("y=%f\n", y);
printf("c=%c\n", c);
return 0;
}
这符合我的预期。我想在转换时测试它的行为。所以一切都没问题,除非我通过这条线将char
转换为float
来让它破裂:
printf("c=%f\n", c);
好的,我正在编译它,这是输出:
~$ cc ex2.c -o ex2
ex2.c: In function ‘main’:
ex2.c:13:3: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("c=%f\n", c);
^
错误清楚地告诉我它无法从int
转换为float
,但这并不妨碍编译器制作目标文件,而且令人困惑的部分就在这里,我运行对象文件:
~$ ./ex2
x=10
y=4.500000
c=c
c=4.500000
如您所见printf
打印之前打印的最后float
值。我使用y
的其他值对其进行了测试,并且在每种情况下都会为y
打印c
的值。为什么会这样?
答案 0 :(得分:1)
您的编译器会警告您有关未定义的行为。任何事情都可能发生。从看似工作到鼻子恶魔的任何事情。关于这个主题的一个很好的参考是What Every C Programmer Should Know About Undefined Behavior。
通常情况下,int可以转换为double就好了:
int i = 10;
double d = i; //works fine
printf
是一种特殊的功能。由于它可以使用任意数量的参数,因此类型必须完全匹配。如果获得char
,则会在传入时将其提升为int
。printf
但是,使用您提供的%f
来获取double
。那不行。
以下是如何实现自己的可变参数函数,取自here:
int add_nums(int count, ...)
{
int result = 0;
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
result += va_arg(args, int);
}
va_end(args);
return result;
}
count
是后面的参数数量。没有被告知,该功能无法知道这一点。 printf
可以从字符串中的格式说明符推断出它。
其他相关部分是循环。它将执行count
次。每次,它使用va_arg
来获取下一个参数。注意它是如何给出va_arg
类型的。假设这种类型。该函数需要依赖调用者传递一些提升为int
的内容,以使va_arg
调用正常工作。
在printf
的情况下,它有一个已定义的格式说明符列表,每个格式说明符都告诉它使用哪种类型。 %d
是int
。 %f
是double
。 %c
也是int
,因为char
被提升为int
,但printf
则需要在形成输出时将该整数表示为字符。
因此,任何采用可变参数的函数都需要一些调用者合作。另一件可能出错的事情是给printf
太多的格式说明符。它将盲目地去获得下一个论点,但没有更多的论点。糟糕,
如果所有这些还不够,那么标准明确说明了fprintf
(它根据C11(N1570)§7.21.6.1/ 9定义printf
:
如果任何参数不是相应转换规范的正确类型,则行为未定义。
总而言之,感谢您的编译器在您未与printf
合作时向您发出警告。它可以为您节省一些非常糟糕的结果。
答案 1 :(得分:0)
由于printf
是一个varargs函数,因此无法将参数自动转换为函数所需的类型。当调用varargs函数时,参数会经历某些标准转换,但这些转换不会在不同的基本类型之间进行转换,例如在整数和浮点之间。程序员有责任确保printf
的每个参数的类型适用于相应的格式说明符。有些编译器会警告不匹配,因为它们会对printf
进行额外检查,但语言不允许它们转换类型 - printf
只是一个库函数,对它的调用必须遵循相同的规则与任何其他功能一样。
答案 2 :(得分:0)
这是一个非常一般的描述,根据使用的编译器可能略有不同......
调用printf("...",a,b,c)
时:
字符串"..."
的地址被压入堆栈。
每个变量a
,b
,c
的值都会被压入堆栈:
当推入堆栈时,短于4个字节的整数值扩展为4个字节。
当推入堆栈时,短于8个字节的浮点值将扩展为8个字节。
程序计数器(或有人称之为指令指针)跳转到内存中函数printf
的地址,并从那里继续执行。
对于传递给函数%
的第一个参数指向的字符串中的每个printf
字符,该函数从堆栈加载相应的参数,然后 - 基于之后指定的类型%
字符 - 计算要打印的数据。
调用printf("%f",c)
时:
字符串"%f"
的地址被压入堆栈。
变量c
的值扩展为4个字节并推入堆栈。
程序计数器(或有人称之为指令指针)跳转到内存中函数printf
的地址,并从那里继续执行。
函数printf
在第一个参数指向的字符串中看到%f
,并从堆栈中加载8个字节的数据。正如您可能已经理解的那样,这会产生垃圾数据&#34;在好的情况下以及在糟糕情况下的内存访问冲突。