【问题标题】:Leaving out forward declarations (prototypes)省略前向声明(原型)
【发布时间】:2014-05-28 04:02:56
【问题描述】:

我即将完成著名的“Learn C The Hard Way”在线课程的第 14 课。

在那一课中,它在C 中介绍了前向声明的概念。 代码示例中有两个前向声明。其中一个可以注释掉,代码仍然可以编译,但是另一个不能注释掉。对我来说,它们看起来同样重要。

这是代码。如果它们来自字母表,它会简单地打印出所有字符及其十六进制代码,否则它会跳过它们。

两个编译器输出位于代码的底部。 有人能解释一下为什么一个出错而另一个没有吗?

#include <stdio.h>
#include <ctype.h>

// forward declarations
int can_print_it(char ch);       //NOT OK to skip(??)
void print_letters(char arg[]);  //OK to skip(??)

void print_arguments(int argc, char *argv[])
{
    int i = 0;

    for(i = 0; i < argc; i++) {
        print_letters(argv[i]);
    }
}

void print_letters(char arg[])
{
    int i = 0;

    for(i = 0; arg[i] != '\0'; i++) {
        char ch = arg[i];

        if(can_print_it(ch)) {
            printf("'%c' == %d ", ch, ch);
        }
    }

    printf("\n");
}

int can_print_it(char ch)
{
    return isalpha(ch) || isblank(ch);
}


int main(int argc, char *argv[])
{
    print_arguments(argc, argv);
    return 0;
}

如果我注释掉第一个前向声明(仅第一个),就会发生这种情况:

cc -Wall -g    ex14.c   -o ex14
ex14.c: In function ‘print_letters’:
ex14.c:24:9: warning: implicit declaration of function ‘can_print_it’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
ex14.c:24:12: note: previous implicit declaration of ‘can_print_it’ was here
make[1]: *** [ex14] Error 1
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'
make: *** [all] Error 2

如果我注释掉第二个声明(仅第二个),就会发生这种情况:

cc -Wall -g    ex14.c   -o ex14
ex14.c: In function ‘print_arguments’:
ex14.c:13:9: warning: implicit declaration of function ‘print_letters’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:17:6: warning: conflicting types for ‘print_letters’ [enabled by default]
ex14.c:13:9: note: previous implicit declaration of ‘print_letters’ was here
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'

【问题讨论】:

  • 其他答案没有明确提及;但是当你调用一个未声明的函数时,它的行为就像有这个声明:int functionname();。这是一个 non-prototype 声明,Floris 所描述的是关于非原型声明中参数计数和提升的规则。这些规则也适用于可变参数原型函数中对应于... 的参数。当函数具有非原型声明但原型定义时,可能会出现与提升相关的错误。

标签: c function


【解决方案1】:

编译器会提示为什么会发生这种情况。关键是这里:

ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration

can_print_it 的参数有一个默认提升,因此它不能有隐式声明。很好的阅读在这里:Default argument promotions in C function calls。基本上,can_print_it (char) 的参数类型与隐式声明一起使用是非法的。要使其工作,您需要使用适当的类型,对于char,它是int。对于其他类型,您可以查看链接的问题和答案。

print_letters 没有这样的参数,它的参数是指针类型。

旁注:正如我们所看到的,如果有 3 个错误的答案,人们就会感到困惑。隐式声明不经常使用并且可能很棘手。 IMO 在一般情况下,或至少在实际应用中,不鼓励使用它们。然而,它们是完全合法的。

【讨论】:

  • 仍然得到-1 =)。我实际上阅读了错误,而不是警告=)。
  • 这是系统中的一个严重缺陷,人们可以匿名给出 -1 并逃跑。
  • 哦,拜托它只有-2。完全没有理由担心。
  • 值得称赞的是,他们删除了答案并删除了 -1。
  • Small nitpick: can_print_it 可以有一个隐式声明,但只有当函数后来用原型定义定义时,它才是未定义的行为,和 i> 该函数实际上是在运行时调用的。
【解决方案2】:

您提供一个函数原型,以便编译器在代码中第一次遇到该函数时知道该做什么。具体来说,如果没有其他信息,编译器会

  • 假设返回值为int
  • 提升参数:
    • “整数类型”到int(例如,char 变为 int
    • float升级为double
    • 指针变成指向int的指针

问题在于,当您将 char 转换为 int 时,有效字节可能会从您认为存储它的位置偏移(例如)3 个字节 - 因为像 @ 这样的值987654330@ 可能存储为0x00000033。根据机器的架构,这会导致问题。

指针也不一样。指针“总是”具有相同的大小,并且总是指向对象的第一个字节(这并不总是正确的......我们中的一些人记得“近”和“远”指针,而不是怀旧)。因此,即使编译器可能认为它正在传递一个指向 int 的指针,后续解释(由未声明的函数)作为指向 char 的指针不会导致问题.

当编译器假定它会返回int 时,您的第二个函数被声明为void 的事实并不重要,因为您从未在赋值或表达式中使用它的返回值(它没有)。因此,即使编译器有点混乱,这只会产生警告,而不是错误。而且由于参数是一个指针,所以提升规则也不会引起冲突。

也就是说 - 在使用函数原型之前总是声明它们是个好主意;一般来说,您应该打开所有编译器警告,并改进您的代码,直到它编译时没有警告或错误。

【讨论】:

  • 需要明确的是,返回值的不匹配会导致未定义的行为;尽管在 OP 的系统上,UB 没有任何不良影响。指针参数不会导致UB,因为没有不匹配。
  • @MattMcNabb - 你是对的。如果您在像x = undeclaredVoid(123); 这样的语句中使用未声明的void 函数,那么编译器将放开它,x 的值将是未定义的(可能是堆栈上的任何内容)。如果你做了一个前向声明,那行甚至不会编译,我想。
猜你喜欢
  • 1970-01-01
  • 2018-01-12
  • 2012-01-19
  • 2010-10-06
  • 1970-01-01
  • 2010-11-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多