【问题标题】:Declaring a function before using it, does it make a difference?在使用它之前声明一个函数,它有什么不同吗?
【发布时间】:2023-12-21 21:11:01
【问题描述】:

这两个函数体代码有什么区别? (第一个代码来自 The C Programming Language 书)

声明函数并使用它与直接使用它而不先声明它有什么区别?

int atoi(char s[]) {
    double atof(char s[]);
    return (int) atof(s);
}

int atoi(char s[]) {
    return (int) atof(s);
}

【问题讨论】:

  • 第一个代码 sn-p 可能是(不推荐)绕过关于缺少 atof 定义的编译器警告的示例。不鼓励这种用法。
  • @Mody El Sayed 在第一个代码 sn-p 中使用了非标准函数 atof。
  • @VladfromMoscow 肯定atof 是标准库函数吗?虽然不如strtofstrtod 强大。
  • atof 的原型是否在范围内?
  • @VladfromMoscow 我明白你现在的意思了:double atof(const char *nptr); 接受 const 参数。

标签: c function declaration


【解决方案1】:

第一个源代码示例明确告诉编译器信息,编译器必须在第二个源代码示例中推断。

以下讨论假设函数atof() 在示例中使用之前未以任何方式(包括文件等)声明或定义。

在第一个示例中,您有一个函数 atof() 的显式声明,它允许编译器执行一些检查它是否被正确使用。

int atoi(char s[]) {
    double atof(char s[]);   // atof is a function that takes a character array and returns a double value
    return (int) atof(s);
}

顺便说一句,我将其声明为 extern double atof(char s[]); 只是为了让人类读者清楚这是一个外部函数。

在第二个示例中,编译器必须推断atof() 是一个函数,并且它需要一个char 数组。编译器使用此调用中使用的源代码行和参数类型对未定义和未声明的函数创建函数声明。

但是编译器不知道函数返回值的类型,因为函数的定义不可用,所以为了完成生成的声明,编译器使用默认的int

此编译器生成的声明仅为函数生成。如果使用未定义或未声明的变量,编译器将生成错误。决定它是函数(可能会生成声明)还是变量(可能不会)的关键在于名称后面是否有左括号。

int atoi(char s[]) {
    return (int) atof(s);
}

第一个带有显式声明的示例为编译器提供了额外的显式信息,允许它检查函数的使用。第二个例子,编译器必须做出假设并生成一个声明,然后用于验证函数是否被正确使用。

这种生成的声明可能导致的一个问题是,如果第一次使用未定义和未声明的函数不正确,编译器生成的声明也是不正确的,如果在下面的源代码中以不同但正确的方式使用该函数,编译器可能会生成警告或错误,具体取决于函数的两种用法(一种不正确,一种正确)之间的实际差异。

对于在其他源文件中使用的函数,首选过程是提供一个头文件或包含文件,该文件与包含正在使用的函数的源代码文件一起提供。在这个头文件中将声明所有具有外部可见性的函数(换句话说,在源文件中没有声明static),并且这个头文件将与包含编译的目标代码或库文件一起提供函数定义。

然后,任何使用该函数的人都将包含头文件,以便编译器检查他们是否正确使用了该函数。有一些检查是编译器无法提供的,或者程序员可以解决这些检查,但如果函数声明可用,则可以通过编译器的静态语法检查提供基本级别的有效性检查。

实验

使用 Visual Studio 2015 进行实验,一旦声明了函数,即使在函数范围内,编译器也会记住该声明并使用该声明来检查源文件中的任何其他用途。此外,如果函数在没有声明的情况下使用,编译器会生成一个声明,然后检查该函数的任何其他使用位置。

记住函数声明的这种行为与在函数范围内声明外部变量的行为有点不同。外部变量声明仅在其声明的范围内可见。此外,如果相同的变量名称存在于封装这个新范围的更高级别的范围中(使用左大括号或左大括号创建),则新声明会覆盖新范围内的先前声明。一旦新作用域结束(使用右大括号或右大括号),原始声明将再次可见。

例如在下面的源代码中,第一个函数中声明的函数atof1()的声明用于验证第二个函数中函数atof1()的使用。此外,当编译器遇到正在使用但未声明的函数时,编译器会为函数atof2() 创建一个声明,并且此创建的声明用于对使用函数atof2() 的任何其他实例执行有效性检查。

int xatoi(char s[]) {
    extern double atof1(char s[]);   // atof is a function that takes a character array and returns a double value
    extern double jj;

    jj = atof1(s);      // line 106
    jj = atof2(s);      // line 107, undeclared undefined function, compiler creates declaration, assumes function returns int
    return (int)jj;     // line 108
}

int xato2(char s[])
{
    int kk;             // line 113
    jj = atof1(s);      // line 114, variable jj is declared in function above but not in this one.
    kk = atof1(s);      // line 115, double value returned by atof1() is converted to int
    kk = atof2(s);      // line 116, compiler uses created declaration to check and atof2() is assumed to return an int
    jj = atof1(s, 3);   // line 117, this use of atof1() does not match the declaration of atof1() in function xatoi() above.
    return (int)jj;     // line 118
}

以上代码在 Visual Studio 2015 中生成以下警告和错误。

1>mldmodd.c(107): warning C4013: 'atof2' undefined; assuming extern returning int
1>mldmodd.c(114): error C2065: 'jj': undeclared identifier
1>mldmodd.c(114): warning C4244: '=': conversion from 'double' to 'int', possible loss of data
1>mldmodd.c(115): warning C4244: '=': conversion from 'double' to 'int', possible loss of data
1>mldmodd.c(117): error C2065: 'jj': undeclared identifier
1>mldmodd.c(117): warning C4020: 'atof1': too many actual parameters
1>mldmodd.c(117): warning C4244: '=': conversion from 'double' to 'int', possible loss of data
1>mldmodd.c(118): error C2065: 'jj': undeclared identifier

【讨论】:

  • 感谢您的解释
最近更新 更多