C变量赋值问题

时间:2014-07-27 14:45:37

标签: c

我认为标题不适合我的问题。 (我很感激,如果有人建议编辑) 我正在学习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的值。为什么会这样?

3 个答案:

答案 0 :(得分:1)

您的编译器会警告您有关未定义的行为。任何事情都可能发生。从看似工作到鼻子恶魔的任何事情。关于这个主题的一个很好的参考是What Every C Programmer Should Know About Undefined Behavior

通常情况下,int可以转换为double就好了:

int i = 10;
double d = i; //works fine

printf是一种特殊的功能。由于它可以使用任意数量的参数,因此类型必须完全匹配。如果获得char,则会在传入时将其提升为intprintf但是,使用您提供的%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的情况下,它有一个已定义的格式说明符列表,每个格式说明符都告诉它使用哪种类型。 %dint%fdouble%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)时:

  • 字符串"..."的地址被压入堆栈。

  • 每个变量abc的值都会被压入堆栈:

    当推入堆栈时,短于4个字节的整数值扩展为4个字节。

    当推入堆栈时,短于8个字节的浮点值将扩展为8个字节。

  • 程序计数器(或有人称之为指令指针)跳转到内存中函数printf的地址,并从那里继续执行。

  • 对于传递给函数%的第一个参数指向的字符串中的每个printf字符,该函数从堆栈加载相应的参数,然后 - 基于之后指定的类型%字符 - 计算要打印的数据。


调用printf("%f",c)时:

  • 字符串"%f"的地址被压入堆栈。

  • 变量c的值扩展为4个字节并推入堆栈。

  • 程序计数器(或有人称之为指令指针)跳转到内存中函数printf的地址,并从那里继续执行。

  • 函数printf在第一个参数指向的字符串中看到%f,并从堆栈中加载8个字节的数据。正如您可能已经理解的那样,这会产生垃圾数据&#34;在好的情况下以及在糟糕情况下的内存访问冲突。