微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我可以调用带长参数和 int 参数的函数吗?

如何解决我可以调用带长参数和 int 参数的函数吗?

这段代码是未定义的行为吗?

extern long f(long x);

long g(int x)
{
    return f(x);
}

根据 C11 标准,在 6.5.2.2 §6 中:

如果函数定义的类型包含原型,并且[...]提升后的参数类型与参数类型不兼容,则行为未定义。

在上面的例子中,函数f被定义为一个包含原型的类型,参数x的类型是int,而参数{{1}的类型是x }} 是 long。根据 6.2.7 §1:

如果类型相同,则两个类型具有兼容类型。

因此,longint 不兼容,因此行为未定义,对吗?

但是,在 6.5.2.2 §7 中:

如果表示被调用函数的表达式的类型确实包含原型,则参数会像赋值一样隐式转换为相应参数的类型,将每个参数的类型视为非限定版本其声明的类型。

如果我正确理解了这一段,这意味着在调用函数时,类型为 x 的参数 int 被隐式转换为 long。根据 6.3.1.3 §1:

一个整数类型的值转换为除_Bool以外的其他整数类型时,如果该值可以用新类型表示,则不变。

由于 int 的等级低于 long,因此每个 int 变量都可以由 long 变量表示。因此,参数 x 可以转换为 long。因此,这不是未定义的行为。

对标准的哪种解释是正确的?我的代码是否为未定义行为?

解决方法

您提供了与代码片段无关的引号。根据同节(6.5.2.2 函数调用)

2 如果表示被调用函数的表达式的类型为 包括原型,参数的数量应与 参数数量。 每个参数都应该有一个类型,使得它的 可以将值分配给具有未限定版本的对象 其对应参数的类型。

函数 f 有一个在调用表达式中可见的原型

extern long f(long x);

和这个作业

int argument;
long parameter;
parameter = argument

是正确的。

至于这个报价

6 如果表示被调用函数的表达式的类型为 不包括原型,整数提升在 每个参数,以及类型为 float 的参数被提升为 双倍的。这些被称为默认参数提升。如果 参数的数量不等于参数的数量, 行为未定义。如果函数定义的类型为 包括一个原型,并且原型以省略号结尾 (,...) 或提升后的参数类型不是 与参数的类型兼容,行为是 不明确的。如果函数定义的类型不 包括一个原型,以及提升后的参数类型 与升级后的参数不兼容, 行为未定义,以下情况除外:

那么就是下面的意思。函数调用表达式看不到函数原型。因此执行默认参数提升。但是在其他地方,函数是用函数原型定义的,并且提升的参数与函数参数不兼容。在这种情况下,您将有未定义的行为。

这是一个与函数调用相关的未定义行为的演示程序。编译器可能会发出错误消息。

#include <stdio.h>

void f();

int main(void) 
{
    short x = 10;
    
    f( x );
    
    return 0;
}

void f( char *s )
{
    printf( "s = %s\n",s );
}

#include <stdio.h>
#include <limits.h>

void f();

int main(void) 
{
    unsigned int x = UINT_MAX;
    
    f( x );
    
    return 0;
}

void f( int x )
{
    printf( "x = %hd\n",x );
}

例如在最后一个程序中调用表达式中的参数x

f( x );

被提升为 unsigned int 类型。但是根据函数定义,该函数需要一个 signed int 类型的参数,并且传递的值不能存储在 signed int 类型中。所以行为是未定义的。 但是您最初的函数调用示例与此引用无关。

,

“提升后的参数”部分令人困惑,它指的是在同一段落中前面定义的默认参数提升。此处不适用,因为这些规则仅在没有原型或我们有可变参数函数时使用。

因此“提升后的参数与参数类型不兼容”适用于您没有原型的情况,应用默认参数提升(整数情况下的整数提升)并且如果类型不兼容然后,存在未定义行为。

但是既然你一个原型,忘记默认参数提升,而是继续阅读下一部分,C17 6.5.2.2/7 强调我的:

如果表示被调用函数的表达式的类型确实包含原型,则参数被隐式转换,就像通过赋值一样,为相应参数的类型,取类型每个参数都是其声明类型的非限定版本。

然后我们去看看关于“好像通过赋值”的说法,C17 6.5.16 强调我的:

左侧操作数具有原子、限定或非限定算术类型,右侧具有算术类型

intlong 都是算术类型(并且没有限定符),这是一种有效的赋值形式。再往下在同一章:

赋值表达式的类型是左操作数的类型 左值转换后。

所以基本上传递参数的代码相当于简单的赋值:

int x;
long y;
y = x;

如果我们让标准让我们在这个快乐追逐中更进一步,接下来查找左值转换,C17 6.3.2.1:

...一个没有数组类型的左值被转换为存储在指定对象中的值(不再是左值);这称为左值转换

然后是整数类型的实际转换,C17 6.3.1.3:

当一个整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则不变。
否则,如果新类型是无符号的,则通过重复添加或转换该值 比新类型可以表示的最大值多减一 直到值在新类型的范围内。
否则,新类型是有符号的,值不能在其中表示;要么 结果是实现定义的或引发了实现定义的信号。

long 始终可以保存 int 的值,因此第一句话是适用于这种情况的转换。

,

代码正确。 IMO,第一种解释不适用。

它实际上是指在没有原型的情况下调用一个带有原型定义的函数:

long g(int x)
{
    return f(x);
}

// other translation unit
long f(long x) {
    return 0;
}

仅当使用与 f 兼容的类型的单个参数调用 long 时才定义代码。

,

在典型的 C 实现中,编译单元独立处理,链接器组合单独编译的目标文件并执行地址重定位而不尝试其他优化,会有一个规范,今天通常称为应用程序二进制接口,其中描述了调用函数的代码应该在哪里/如何存储参数,以及函数应该在哪里/如何找到其调用者存储的参数。

如果函数试图以与其调用者存储参数的方式不一致的方式检索参数,则结果可能没有意义。另一方面,许多平台 ABI 根据存储格式而不是 C 数据类型来描述行为。因此,在例如一个 32 位 ARM 实现,其中 intlong 都是 32 位数据类型,期望 int 的函数将以与期望 long 完全相同的方式调用{1}} 或 int32_t。因此,如果一个编译单元使用类型 int 而另一个使用 long 类型,那么独立处理不同编译单元中的代码的 ARM 实现不需要关心这两种类型是否具有相同的表示。

一般来说,即使在不关心这些事情的实现上,也应该在实际情况下使参数/参数类型匹配,因为它会让人们更容易阅读代码并知道它在做什么。然而,在某些情况下,可能有不同的编译单元,这些单元期望得到指向函数的指针,这些函数的参数是具有匹配表示的不同类型。在这种情况下,如果使用单独处理编译单元的实现,则可以将单个函数的地址传递给两个编译单元中的代码。不幸的是,程序员无法指示何时需要以与 ABI 一致的方式处理函数调用,而不考虑标准是否会定义其行为,并且一些积极的优化器不会尝试有意义地处理构造其行为将由 ABI 而非标准定义。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。