【问题标题】:C - extern, static, includeC - 外部、静态、包含
【发布时间】:2015-03-10 15:46:34
【问题描述】:

C 中的作用域非常混乱。我有一个变量:“int qwe”。这个 var 应该在一个或多个文件中可见 - 在本例中为 f1.c,但在另一个 f2.c 中不可见。

假设我有:main.c、f1.c、f2.c、header.h

主要: 调用 f1(); 调用 f2();

标题:

#ifndef HEADER_INCLUDED
#define HEADER_INCLUDED

int qwe = 1;
void f1();
void f2();
#endif // HEADER_INCLUDED

f1.c:

#include <stdio.h>

extern int qwe;

void f1(){
    printf("In f1: %d\n", qwe);
}

f2.c:

#include <stdio.h>

static int qwe = 2;

void f2(){
    printf("In f2: %d\n", qwe);
}

现在这变得令人困惑。有定义和声明。我在标题中定义了 qwe,在 f1.c 中声明了它。那是对的吗?是否应该在 f1.c 的标题和声明中进行定义?我尝试了后一种情况,但得到一个错误 - “qwe 的多重定义”。当我从 f1.c 中删除 #include 指令时,它起作用了……当我删除 extern 关键字时它也起作用。 extern 是多余的吗?

f2.c 我想它没问题并且表现如预期,或者是吗?但是,如果我在标题中添加#include,它就会中断。这是为什么呢?

我什么时候应该在源文件中使用#include?如果我不包括它 f1.c 或 f2.c 它可以工作...

另外,如果我在函数中将变量定义为静态变量,例如 static int i = 0;这个变量在函数存在时不会被破坏,它会保留它的内存和数据。下次调用相同的函数时,将可以访问它,对吗?但是 var 不会重新初始化为 0,即定义的行不会执行。对吗?

生活让我窒息:(

【问题讨论】:

  • 忘记明确说明如果将#include "header" 放入f1.c,编译失败并报错:"multiple definition of qwe" and "first defined here"。这是为什么呢?
  • 头文件应该包含声明,而不是定义。将定义放在 f1.c 中。不要在 f2.c 中包含头文件或使用 #define qwe qwehidden 隐藏声明
  • 经验法则:如果您发现自己对非常量变量使用了extern 关键字,那么您的程序设计就会被破坏。

标签: c static extern scoping


【解决方案1】:

在标题中,声明变量。在例如f1.c 中,定义变量,例如int qwe = 1; // 在全局范围内。

在所有想要查看/更改qwe 的文件中,包括声明变量的标头。简单易懂。

【讨论】:

    【解决方案2】:

    您需要在标题中声明变量,并将其定义在一个且只有一个C文件中。

    在 C 语言中,您不能有一个不“属于”给定翻译单元(源文件)的变量。所以它必须由整个程序中的一个翻译单元来定义。

    当您将变量声明为extern 时,您是在告诉编译器该符号(可能)在您的翻译单元(c 文件)之外。

    可能还值得注意的是,当您尝试声明一个变量时没有 extern,该变量也已定义,例如:

     /* Declares, but does not defines external symbol 'foo' */
     extern int foo;
    
     /* Both declares AND defines bar */
     int bar;
    

    这也不同于函数的工作方式,声明的“默认”语法没有定义函数:

     /* Declare, but don't define spam */
     void spam(void);
    
     /* Declare, but don't define eggs */
     extern void eggs(void);
    
     /* Declare & define 'cheese' */
     void cheese(void){ return; }
    

    所以你的例子应该更像这样:

    qwe.h:

    #ifndef QWE_H
    #define QWE_H
    /* Declare qwe here */
    extern int qwe;
    #endif
    

    f1.c:

    /* DEFINE qwe here */
    int qwe = 1;
    

    f2.c:

    #include "qwe.h"  /*header includes the `extern int qwe` declaration */
    void my_function(void)
    {
       /* use external symbol here! */
       qwe = 10;
    }
    

    【讨论】:

      【解决方案3】:

      范围系统并不那么令人困惑。规则是这样的:

      • 如果您在 .c 文件中定义了某些内容,则程序中的每个其他 .c 文件都可以访问它(它位于全局命名空间中)。

      • 如果您在其定义前指定静态,则只有 SAME 文件中的内容可以看到它。这应该是您不希望其他 .c 文件能够访问的所有函数和变量的默认位置。

      记住,extern 只告诉编译器所讨论的变量/函数未在当前文件中定义,这一点非常重要。这可以防止编译器因为找不到符号而发出错误,但它与范围无关 - 您的 .c 文件会看到全局命名空间中的所有内容,如果您没有遵循规则的第二部分,您将在链接时很快发现这一点。

      头文件同样与范围无关。它们只是放置一堆 extern 语句和宏的方便位置。

      【讨论】:

        【解决方案4】:

        在 C 语言中,您通常应该使用头文件来声明数据,而不是定义数据。您不想在标头中定义全局数据,因为它将在多个模块中冗余定义。标头向多个模块指示某处存在某些数据或函数及其类型,以及公共常量和宏 (#defines)。除此之外,C 中的东西非常简单。从技术上讲,几乎任何东西都是全局的,除非您将其声明为 static,并将其范围限制在它 定义 所在的模块内。标题和函数原型中数据的 extern 声明有助于编译器知道这些项目正在被特定模块访问以及要访问的数据类型是什么,以便可以生成正确的代码。

        你有:

        • 两个函数 f1f2 在单独的模块中定义,但在 main使用。所以这些需要在头文件中声明

        • 一个全局数据项qwe在多个模块中使用。

        • 一个静态qwe在一个模块中使用。

        假设您希望使用一个头文件完成此操作(f1f2 和全局数据可能需要单独的头文件 - 见下文),您可以设置 您的标题为:

        #ifndef MY_HEADER_H
        #define MY_HEADER_H
        
        extern int qwe;
        
        void f1(void);
        void f2(void);
        #endif // MY_HEADER_H
        

        然后在你的main.c:

        ...
        #include "my_header.h"
        
        int qwe;    // This is global and can be accessed from other modules
        
        void main(...)
        {
            // call f1
            // call f2
           ...
        

        我刚刚在main.c中任意定义了全局变量qwe。例如,如果您有一些全局变量,您可以在它们自己的 glob_data.c 模块中定义它们,并拥有它自己的标头 glob_data.h声明它们。需要访问它们的任何其他模块都将包含 glob_data.h 标头,以便可以在该模块上正确完成编译以访问该数据。将全局数据保存在单独的标头中有助于解决诸如 static 数据实例与全局冲突的情况。当您想使用static 项进行编译时,您可以避免包含该全局项的数据头文件。

        然后在你的 C 文件中,f1.c:

        ...system headers included...
        #include "my_header.h"
        
        void f1() {
            printf("In f1: %d\n", qwe);
        }
        

        f2.c:

        ...system headers included...
        #include "my_header.h"  // Only if it doesn't contain `extern int qwe;`
        
        static int qwe = 2;   // This hides the global qwe and is known only
                              // to f2.c
        
        void f2(){
            printf("In f2: %d\n", qwe);
        }
        

        正如我上面提到的,您可能希望将函数原型和全局数据声明分开放在单独的标头中。这样,您可以只在每个模块中包含需要的内容并避免冲突,例如当您有 static int qwe; 与全局 int qwe; 时。

        【讨论】:

        • 首先,谢谢大家的澄清。我正在使用的书对于这些事情不够冗长或假设更高的智能水平:D @lurker 现在,我已经尝试过这个并在 f2.c 中遇到错误,说“qwe 的静态声明遵循非静态声明”并且“先前的 qwe 声明就在这里”。它是通过删除#include 指令来修复的。
        • @user2092119,该错误是一个很好的理由将数据头与extern in qwe;分开,这样就不必包含它,但如果你想包含声明f1 和/或f2
        【解决方案5】:

        我在标题中定义了qwe,并在f1.c 中声明了它。那是对的吗?

        不,应该反过来。您应该在单个翻译单元中定义全局变量(对于 .C 文件来说这是极客说话),但您可以根据需要在任意多个翻译单元中声明它。由于标头可能包含在许多翻译单元中,因此声明位于标头中。

        我什么时候应该在源文件中使用#include?​​p>

        当标题包含您的翻译单元编译所需的任何内容时,您会这样做,只有极少数例外。请注意,在某些情况下,可能需要或希望手动进行前向声明而不包括标题。

        另外,如果我在函数中将变量定义为静态变量,比如static int i = 0;,这个变量在函数存在时不会被销毁,它会保留它的内存和数据。

        没错,函数内部的静态变量只会被赋予一次初始值,并且只要你的程序继续运行,它就会保留你赋予它的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-13
          • 1970-01-01
          • 1970-01-01
          • 2022-12-04
          • 2012-06-23
          • 1970-01-01
          相关资源
          最近更新 更多