【问题标题】:Old style C function declaration旧式 C 函数声明
【发布时间】:2011-06-02 16:01:36
【问题描述】:

这是一个使用旧式语法定义和定义的简单函数:

#include <stdio.h>
void
error(message,a1,a2,a3,a4,a5,a6,a7)
        char *message;
        char *a1,*a2,*a3,*a4,*a5,*a6,*a7;
{
  fprintf(stderr,message,a1,a2,a3,a4,a5,a6,a7);
}
int main ()
{
  error("[ERROR %d]: %s.\n",110,"Connection timed out");
  return 0;
}

可以编译并正确运行打印:

[ERROR 110]:连接超时。

我读到这种风格没有关联原型,但是它如何在运行时自动将 int 转换为 char * 甚至提供的参数比它声明的要少?

【问题讨论】:

    标签: c function coding-style runtime


    【解决方案1】:

    其实有一个110到int的转换,这个转换是由fprintf完成的,当fprint读取“%d”时,它会尝试将对应的参数转换为int。另一点是函数期望指针,即内存地址,并且指针是整数。如果传递的是字符串而不是 110,仍然会打印一个数字,即运行时字符串的地址。

    【讨论】:

    • printf 系列不进行任何类型转换。您必须给出正确的说明符。
    • printf 和类似函数根本不以这种方式进行转换。参数作为它们的类型传递。
    【解决方案2】:

    实际上,如果编译器在遇到函数error() 的定义之前遇到它的使用,它实际上确实在范围内有一个原型(这就是为什么老 C 程序员经常根据使用顺序在文件中对函数定义进行排序的原因)。因此,110 可以转换为(char*)110(这在sizeof(int) == sizeof(char*) 所在的机器上并不重要)。看看在sizeof(int) != sizeof(char*) 的机器上会发生什么会很有趣。

    【讨论】:

    • 有一台 x86_64 机器吗?你有一台机器,sizeof(int)!=sizeof(char *) 是的,代码应该仍然可以工作。
    • 这段代码中error的声明不是原型。没有“有效地拥有原型”这样的东西——它有或没有,在这种情况下它没有。 110 不会被转换。您似乎假设所有函数声明都是原型;但这是一个不是的例子。
    • 旧式函数不是原型这一事实是不使用它们的原因。如果没有原型,将不会检查传递给函数的参数 - 编译器将假定函数接受代码传递给函数的任何参数。这也是为什么它实际上通过了编译,因为函数调用使编译器假定该函数需要三个参数,一个char*,一个int 和一个char*——这取决于你的想象力来弄清楚这有多大的错误得到。
    【解决方案3】:

    传递的参数太少或类型错误(你都做了)会导致未定义的行为。这就是为什么你永远不应该在新代码中使用旧式语法的原因。如果您使用新语法,您将从函数定义中获得“免费”原型。换句话说:

    void
    error(char * message,
    char * a1, char * a2, char * a3, char * a4, char * a5, char * a6, char * a7)
    {
    
    }
    

    也是一个原型。

    使用旧语法,您必须提供自己的语法,而您没有。这意味着编译器无法检查调用。

    实际上(在您的机器上),error 正在将堆栈中的int 读入char *。然后,它将char * 传递给fprintf。但是使用了%d 说明符,因此fprintf 将其作为int 弹出。这是更多未定义的行为。但它恰好在你的机器上工作; char *int 的大小可能相同。

    error 还从堆栈中读取 5 个垃圾 char * 值。然后它将这些传递给fprintf,它会忽略它,因为只有两个转换说明符。

    【讨论】:

    • 我明白了。我在阅读核心实用程序时遇到了这个 sn-p。它用于使用不支持可变参数函数的旧 c 方言的机器。
    • @Ox, stdarg 是 21 年前标准化的。很少有使用仍然不支持 C89 的编译器。
    • @Mathew,没错,但并非所有代码都是新的。找到具有悠久历史的古老代码仍然提供允许其在旧编译器上使用的机制的情况并不少见。
    • @RBerteig,这就是我说“在新代码中”的原因。 core-utility 可能曾经需要它。但是新代码可能不应该复制这个 sn-p。
    【解决方案4】:

    基本上,它之所以有效,是因为它太笨了,无法更好地了解。老式的 K&R C 基本上不检查任何东西。你侥幸逃脱,因为,

    1. 碰巧sizeof(int) == sizeof(char *) 在您使用的特定架构和编译器组合上。它并没有真正转换任何东西,它只是认为 32 位是 32 位。

    2. 1234563当电话返回时,它们就会消失,没有人更聪明。但是,如果您碰巧尝试打印七个值而您只传递了六个参数,它会在运行时爆炸,有时会以创造性和意想不到的方式发生。

    【讨论】:

    • 对于第 1 点,即使我将其更改为 error("[ERROR %d %c]: %s.\n",110,'k', "Connection timed out");有用。 sizeof(int) != sizeof(char) 在我的机器上。
    • @Ox,但 char 在函数参数中变为 int,因为默认参数提升 (§6.5.2.2/6),其中包括整数提升 (§6.3.1.1/2 )。
    • Ox,你没有仔细阅读——我说的是 sizeof(int) == sizeof(CHAR STAR),即指向 char 的指针的大小。马修,你对促销的看法是对的,但请注意,参数是指向字符的指针,而不是字符。
    • 它与大小无关,即使sizeof(int)!=sizeof(char *) 仍然有效
    • 废话。试试吧。由于您无法控制体系结构,因此请尝试将双精度值传递给浮点数。如果大小不匹配,堆栈展开错误并发生坏事。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-27
    • 2012-02-29
    • 2016-08-10
    • 1970-01-01
    相关资源
    最近更新 更多