允许在不告知数据类型的情况下传递变量?

时间:2014-03-31 12:05:02

标签: c

我有以下代码成功执行,但是当函数printit没有传递变量的数据类型时,它怎么可能。然后为什么输出浮点值正确打印但字符值不正确。我甚至试图将其类型化为char

main( )
{
    float a = 15.5 ;
    char ch = 'C' ;
    printit ( a, ch ) ;
}

printit ( a, ch )
{
    printf ( "\n%f %c", a, ch ) ;
}

它的执行给出了结果:

15.500000 ╠

1 个答案:

答案 0 :(得分:3)

在预标准C中,这是一种允许的,但即使是不鼓励的编写代码的方式。没有原型;在没有类型说明符的情况下,变量的类型为int

如果类型不是int,您可以写:

print_it_2(a, b, c)
    struct dohickey *c;
    double b;
{
    printf("%d %p %f\n", a, b, c);
}

请注意,变量在一个订单中列出并在另一个订单中定义,而a仍然是int,因为它根本没有列出。该函数的返回类型也被假定为int。但是,特别是如果函数毕竟没有返回值(这是在支持返回类型void之前),人们将从声明中省略返回类型,而不从函数返回值。

这不再是好风格。如果编译器没有抱怨它,它应该。我用额外的选项编译代码(尤其是关于SO的答案):

gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
        -Wold-style-definition -Werror ll3.c -o ll3

缺少的原型和严格的原型以及旧样式定义是关于消除这些旧的预标准代码的最后痕迹。我并不总是(通常)在我的答案中显示这三个选项,但是当我编译代码时它们就在那里。这意味着你会看到函数static(以避免缺少原型)和int main(void)而不是int main()(以避免严格的原型),并且旧样式定义会大喊大叫所讨论的函数问题。


问题的第二部分是'为什么浮动值正确打印而字符不是'。

非原型功能的一种变化方式是自动促销适用。任何小于int的类型(charshort)都会提升为intfloat会提升为double(稍微简化) 。因此,在通话printit(a, ch)中,实际传递的值为doubleint。在函数内部,代码认为aint(具体而言,4字节int),并且ch同上。这两个4字节的数量被复制到堆栈中(再次,稍微简化),并传递给printf()。现在,printf()是一个可变函数(接受可变数量的参数),并且作为省略号...的一部分传递的参数也会自动提升。所以在printf()里面,它被告知堆栈上有8个字节包含double值,果然,调用代码传递了8个字节(尽管是两个4字节单元),所以{ {1}}被神奇地保存了下来。然后double被告知堆栈上有一个额外的printf(),但调用代码没有放置int,所以它会读取其他一些不确定的值并打印该字符。 / p>

这种混乱是原型所阻止的。这就是为什么你再也不应该以这种方式编码了(这一千年的所有情况都是如此,甚至在上一个千年的最后十年的后半段也是如此)。


Peter Schneidercomment

中提问
  

您是否可以指出允许或禁止未申报参数的相关标准?如果确实被禁止,为什么int在被要求时不符合标准?

在Mac OS X 10.9.2上使用该代码并使用GCC 4.8.2进行编译,我得到:

gcc

$ gcc -std=c11 -c xyz.c xyz.c:1:1: warning: return type defaults to ‘int’ [enabled by default] print_it_2(a, b, c) ^ xyz.c: In function ‘print_it_2’: xyz.c:1:1: warning: type of ‘a’ defaults to ‘int’ [enabled by default] xyz.c:5:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] printf("%d %p %f\n", a, b, c); ^ xyz.c:5:5: warning: incompatible implicit declaration of built-in function ‘printf’ [enabled by default] $ 给出了相同的警告。但是:

-std=c99

通过提供$ gcc -std=c89 -c xyz.c xyz.c: In function ‘print_it_2’: xyz.c:5:5: warning: incompatible implicit declaration of built-in function ‘printf’ [enabled by default] printf("%d %p %f\n", a, b, c); ^ $ ,即使这样也会被平息。

ISO / IEC 9899:2011第6.9.1节涵盖了功能定义(与C99中的相同部分编号)。但是,这两个都排除了C89必须允许的“隐式#include <stdio.h>”行为(因为很多预先存在的代码没有听说过这些规则。

然而,即使C11也允许旧样式定义(具有完整类型):

  

函数的定义:
      declaration-specifiers declarator declaration-list opt compound-statement

     

声明列表:
      声明
      声明列表声明

'declaration-list opt '是函数声明符和作为函数体的复合语句之间的类型列表。

C11也说:

  

§6如果声明者包含一个标识符列表,则声明列表中的每个声明都应该   至少有一个声明者,那些声明者只能声明来自的标识符   标识符列表,标识符列表中的每个标识符都应声明。

我无法立即找到我的副本“The Annotated C Standard”,这本书包含了左侧页面上的有用信息(1989/1990 C标准),并且右侧信息通常不太有用。每个传播的页面。这将允许我引用标准,但目前它是AWOL。

在本段第6段中,C89的措辞不那么严格。

至于为什么int不拒绝粗俗的旧式代码,答案仍然是“向后兼容性”。那里还有旧的代码,它允许它在可能的情况下进行编译,除非它的手被gcc小心地绑在背后。