【问题标题】:Passing float to a function with int argument (that is not declared beforehand)将 float 传递给具有 int 参数的函数(未事先声明)
【发布时间】:2017-03-02 07:52:02
【问题描述】:

我已阅读Garbage value when passed float values to the function accepting integer parameters 的答案。我的问题更深入一点。我也可以问我有超过 50 的声望点。我正在添加我的代码以获得更多说明:

#include <stdio.h>
#include <string.h>

void p2(unsigned int tmp)
{
    printf("From p2: \n");
    printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}

int main()
{
    float fvar = 45.65;

    p1(fvar);
    p2(fvar);
    printf("From main:\n");
    printf("sizeof(int) = %lu, sizeof(float) = %lu\n", sizeof(int),
            sizeof(float));
    unsigned int ui;
    memcpy(&ui, &fvar, sizeof(fvar));
    printf("fvar = %x\n", ui);
    return 0;
}

void p1(unsigned int tmp)
{
    printf("From p1: \n");
    printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}

输出是:

From p1: 
tmp = 1 ,In hex tmp = 1
From p2: 
tmp = 45 ,In hex tmp = 2d
From main:
sizeof(int) = 4, sizeof(float) = 4
fvar = 4236999a8

float 值传递给预先声明的带有int 参数的函数(即p2)会得到正确的结果。当尝试对未事先声明的函数(即 p1)进行相同操作时,会给出不正确的值。而且我知道编译器不会为之前未声明的函数假定任何类型或参数的原因。这就是为什么在 p2 的情况下float 值不会被类型转换为int

我的困惑是,在 p2 的情况下,float 值究竟如何被复制到本地 int 变量 tmp。

如果它是“逐位复制”,那么读取这些位置应该至少产生十六进制的东西(除了 1)(如果不是整数)。但这听起来不像输出显示的那样。我知道浮点表示是不同的。

p2 如何读取浮点数未复制到的寄存器/堆栈位置?正如链接问题中的 simonc 所建议的那样?

我已经包含了intfloat 的大小,如果有帮助的话,我的编译器是 gcc。

【问题讨论】:

  • 这是未定义的行为。不要试图推理它。根据定义,它没有定义会发生什么。
  • 如果你想在调用函数之后定义一个函数你必须在调用函数之前添加一个原型。就是这样
  • 详细说明@LPs 所说的,没有那个,你的代码就不再是一个有效的 C 代码了。
  • 另外看看compiler errors
  • 您正在尝试调用尚未声明的函数,因此它会自动声明为int p1()。即使您稍后将其定义为void p1(unsigned int tmp),它也已被声明为int p1()(不带任何参数)。我很确定编译器会为此发出警告和错误,这些错误不应被忽略。

标签: c


【解决方案1】:

C 编程语言本质上是一种单扫描语言 - 编译器不需要重新读取代码,但它可以逐行汇编代码,只保留有关如何声明标识符的信息。

C89 标准有隐式声明的概念。在没有声明的情况下,函数p1 被隐式声明为int p1();即返回 int 并采用未指定参数的函数,这些参数通过默认参数提升。当您调用这样的函数并为其提供 float 作为参数时,float 参数将提升为 double,正如默认参数提升所要求的那样。如果函数是int p1(double arg)就好了;但是预期的参数类型是unsigned int,并且返回值也不兼容(void vs int)。这种不匹配将导致程序具有未定义的行为 - 推理当时发生的事情是没有意义的。但是,如果编译器不支持过时的隐式声明,许多旧的 C 程序将无法编译 - 因此您只需将所有这些警告视为错误。

请注意,如果您将p1返回值 更改为int,您将收到更少的警告:

% gcc implicit.c
implicit.c:14:5: warning: implicit declaration of function ‘p1’ [-Wimplicit-function-declaration]
 p1(fvar);
 ^~

但是在我的编译器上观察到的行为基本相同。

因此,仅存在警告:函数'x'的隐式声明很可能是新编写的代码中的严重错误

如果函数在使用前声明,如p2,那么编译器知道它期望unsigned long作为参数,并返回void,因此它会知道生成正确的转换代码从floatunsigned long 的参数。


C99 和 C11 不允许在严格符合的程序中进行隐式函数声明 - 但它们也不需要符合标准的编译器来拒绝它们。 C11 说:

标识符是主要表达式,前提是它已被声明为指定一个对象(在这种情况下它是一个左值)或一个函数(在这种情况下它是一个函数指示符)。

还有一个脚注说明

因此,未声明的标识符违反了语法。

但是,它不需要编译器拒绝它们。

【讨论】:

  • 我明白了。这是未定义的行为。我喜欢你的解释。我有一个问题:为什么 C 标准允许它?这种未定义的行为不应该首先给出错误(即使不使用-Werror)?还是在任何情况下都有用?
  • “但是,如果编译器不支持过时的隐式声明,许多旧的 C 程序将无法编译 - 因此您只需将所有这些警告视为错误。”跨度>
  • 在 70 年代,隐式函数声明就是这种情况:D 阅读有关隐式函数声明的更多信息 here。请注意,该问题具有双重未定义行为,因为它正在重新定义也具有未定义行为的内置函数。
【解决方案2】:

这个,

void p1(unsigned int tmp);

应该是implicitly declared

int p1();

由编译器。

虽然编译器不会抛出错误,但它应该被视为一个错误,您可以在链接的帖子中阅读。

无论如何,这是未定义的行为,您不能指望可预测的输出。

【讨论】:

  • 它将被隐式声明为int p1()
【解决方案3】:

在二进制级别,floatint 看起来根本不一样。

当尝试将float 复制到int 时,会发生隐式转换,这就是为什么当您调用以int 作为参数但提供float 的函数时,您会得到它的整数部分,但在最后的测试中,你会看到它看起来有多丑。这不是垃圾,如果您以十六进制打印,这就是 float 在内存中的样子。详情请见IEEE 754

然而,p1() 的问题在于您正在尝试调用尚未声明的函数,因此它会自动声明为 int p1()。即使您稍后将其定义为void p1(unsigned int tmp),它也已被声明为int p1()(不带任何参数),因此它不起作用(行为未定义)。我很确定编译器会为此发出警告和错误,这些错误不应被忽略。

请注意,声明和定义函数之间存在很大差异。稍后定义函数是完全合法的,就像你正在做的那样,但是如果你想让它正常工作,就必须在尝试使用它之前声明它。

例子:

// declare functions
void p1(unsigned int tmp);
void p2(unsigned int tmp);

// use functions
int main()
{
    p1(1);
    p2(1);
}

// define functions
void p1(unsigned int tmp)
{
    // do stuff
}
void p2(unsigned int tmp)
{
    // do stuff
}

【讨论】:

    猜你喜欢
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    • 1970-01-01
    • 2019-12-19
    • 2013-08-03
    • 2016-07-19
    • 2014-10-31
    相关资源
    最近更新 更多