函数内部的函数声明 - 为什么?

Max*_*Max 23 c function

我正在阅读"C语言编程"一书,并在第10章中找到了这样一个例子:

#include <stdio.h>

void test (int  *int_pointer)
{
     *int_pointer = 100;
}

int main (void)
{
     void test (int  *int_pointer);
     int  i = 50, *p = &i;

     printf ("Before the call to test i = %i\n", i);

     test (p);
     printf ("After the call to test i = %i\n", i);

     return 0;
}
Run Code Online (Sandbox Code Playgroud)

我理解这个例子,但我不明白其中void test (int *int_pointer);的内容main.为什么我要test再次定义签名?这是惯用的C吗?

hac*_*cks 11

void test (int *int_pointer);只是函数的声明(或原型)test.不需要这个声明main因为你之前已经有了函数定义main.

如果定义test是在main那之后那么值得将其声明放在那里让编译器test在调用它之前知道返回类型,参数数量和参数类型.


han*_*rak 11

它绝对不是惯用的C,尽管它是完全有效的(多个声明是可以的,多个定义不是).这是不必要的,所以如果没有它,代码仍然可以正常工作.

如果有的话,也许是作者的意思

void test (int *int_pointer);

int main (void) {

    ...

}
Run Code Online (Sandbox Code Playgroud)

以防函数定义放在后面main ().

  • 我也不会把你的两个链接称为可靠的,而`int main(void)`是完全惯用的.也许你应该打开警告,看看为什么你更喜欢`void`而不是未使用的参数. (13认同)
  • 使用`-Wall -Wextra`,它们确实警告未使用的`argc`和`argv`与任何其他函数一样.哪个是好的,因为当你不使用命令行参数时,无论如何都要使用`int main(void)`.只是因为困扰你的事并不意味着它不是惯用的. (12认同)
  • 我想说的是:`(void)`很好,标准和惯用.错误地引用另一个答案而烦恼你并没有改变它. (9认同)
  • 请注意,HowStuffWorks引用和Y分钟中的Learn X,其中X = C引用使用`int main()`,它不支持关于`main()`签名的论文.C标准也不支持你的论文; 它明确地说`int main(void)`是可以的. (8认同)
  • @haneefmubarak你正在争论一个简单而明显错误的观点*:C标准的确切词是"在程序启动时调用的函数被命名为`main`.该实现声明没有这个函数的原型.它应该定义为返回类型为"int"且**没有参数**".然后继续描述`argc` /`argv`案例和其他选项,但`(void)`*字面*是它描述的第一件事; 不可能得到*更多*惯用语.(这甚至与OP无关!) (5认同)
  • 这个答案有误导性.行**"但是,当正确写入时,main()应始终将`argc`和`argv`作为参数,即使不使用.."**在这种情况下完全错误且无关紧要. (5认同)
  • 由于函数声明,我并不是说这本书不可靠.相反,它是"main()"的声明和定义,因为它使我感到烦恼.虽然它在技术上是可接受的[按照[C89和C11,它是不好的形式/非惯用并且不鼓励](http://stackoverflow.com/a/3711075/2334407). (2认同)
  • @haneefmubarak:无论如何都应该为大多数模块启用-Wextra.值得注意的是,关于未使用的参数/本地的警告非常有帮助.例如,声明main(void)是嵌入式系统的常见做法,因为根本没有命令行参数(甚至不返回).引用一个被取代的标准是一个弱论点,甚至更新,因为新标准明确允许这一点. (2认同)
  • 关于`main` C标准的定义说:*"在程序启动时调用的函数名为`main`.实现声明没有这个函数的原型.它应该被定义为返回类型`int`而没有参数:`int main(void){/*...*/}`或两个参数(这里称为`argc`和`argv`,虽然可以使用任何名称,因为它们是函数的本地名称)它们被声明):`int main(int argc,char*argv []){/*...*/}`或等价物; 10)或其他一些实现定义的方式."*--5.1.2.2 .1程序启动 (2认同)

Yu *_*Hao 9

它不是C语言,但仍然有效.

该行是函数的声明test,而不是定义.函数不能多次定义,但有多个声明是有效的.


Leu*_*nko 5

它完全是惯用的C,它实际上具有(有限的)实际用途 - 尽管不是这个例子所证明的.

当您在通常的全局级别声明函数或其他名称时,它将被引入声明后面的代码中的所有函数体.一旦引入声明,就无法从作用域中删除声明.该功能对翻译单元的其余部分始终可见.

支撑块中声明函数或其他名称时,声明的范围仅限于该块.声明另一个函数范围内的函数将限制其可见性,并且不会污染全局命名空间或使其对同一翻译单元中定义的任何其他函数可见.

在示例的情况下,这是毫无意义的,因为定义test也将它带入所有后续主体的范围 - 但如果test在另一个转换单元中定义,或者即使它仅在此TU的最底部定义,则隐藏声明内部main将保护之后定义的任何其他函数,使其无法在其范围内查看其名称.

实际上,这是有限的使用 - 通常如果你不想看到一个功能,你把它放在另一个翻译单元(最好是它static) - 但你可能会设想你可能想要使用它的情况构建不导出其组件的原始声明的模块加载系统的能力,或类似的东西(以及这不依赖于static/单独的目标文件的事实可能与嵌入/非托管的某些相关链接步骤可能无法像在PC上那样工作的目标环境,允许您在纯粹#include的构建系统中实现名称空间保护措施.

例:

struct module {
    void * (* alloc)(size_t);
    void (* dealloc)(void *);
} loaded_module;

int main(void) {
    if (USE_GC) {   // dynamically choose the allocator system
        void * private_malloc_gc(size_t);
        void private_free_noop(void *);
        loaded_module = (struct module){ private_malloc_gc, private_free_noop };
    } else {
        void * private_malloc(size_t);
        void private_free(void *);
        loaded_module = (struct module){ private_malloc, private_free };
    }
    do_stuff();
    //...
}

// cannot accidentally bypass the module and manually use the wrong dealloc
void do_stuff(void) {
    int * nums = module.alloc(sizeof(int) * 32)
    //...
    module.dealloc(nums);
}

#include "allocator_implementations.c"
Run Code Online (Sandbox Code Playgroud)

  • @Leushenko:它不是关于范围,而是关于一致性和可靠性.如果在源文件中声明外部函数,在函数内的全局作用域或局部作用域中,此声明只是程序员的一个承诺,编译器无法轻易检查.@Jonathan Leffler说这样的函数声明属于一个头文件,它包含在所有模块中,这些模块在定义其实现的模块中使用函数**和**来检查一致性.`gcc`和`clang`有警告强制执行这个有用的约定并防止代码生成不一致. (2认同)
  • 在函数范围内具有外部声明的典型用法是具有大(外部)查找表的内联函数.另一个是系统级函数,它引用链接器提供的符号,例如heap_start.这些只需要在受影响的功能中可见.当然,如果更多功能需要这些功能,CU范围将是优先考虑的.但是,在外部头文件中使用它仍然没有任何好处. (2认同)