静态变量不仅限于编译单元吗?

时间:2017-12-30 08:06:40

标签: c

我有以下文件:

main.c

#include "ext.h"
#include "main2.h"
#include <stdio.h>

int main () {
  // printf("main - internal_static_variable: %d\n", internal_static_variable);
  // printf("main - internal_static_variable: %d\n", internal_static_variable);
  printf("main - external_variable: %d\n", external_variable);

  put_static_val(24);
  put_val(42);

  printf("main - internal_static_variable: %d\n", get_static_val());
  printf("main - internal_variable: %d\n", get_val());

  ++external_variable;
  print();
}

main2.h

// main 2.h

#pragma once

void print();

main2.c

// main2.c

#include "ext.h"
#include "main2.h"
#include <stdio.h>

void print() {
  printf("main2 - external_variable: %d\n", external_variable);
  printf("main2 - internal_static_variable: %d\n", get_static_val());
  printf("main2 - internal_variable: %d\n", get_val());
}

ext.h

// ext.h

#pragma once

extern int external_variable;

void put_static_val(int v);
int get_static_val();
void put_val(int v);
int get_val();

ext.c

// ext.c

#include "ext.h"

static int internal_static_variable = 0;
int internal_variable               = 1;
int external_variable               = 2;

void put_static_val(int v) {
  internal_static_variable = v;
}

int get_static_val() {
  return internal_static_variable;
}

void put_val(int v) {
  internal_variable = v;
}

int get_val() {
  return internal_variable;
}

编译和执行时,结果如下:

main - external_variable: 2
main - internal_static_variable: 24
main - internal_variable: 42
main2 - external_variable: 3
main2 - internal_static_variable: 24
main2 - internal_variable: 42

正如预期的那样,头文件(internal_static_variableinternal_variable)中未公开的变量无法直接访问。

我没有得到的是static的含义。我知道它将变量的范围限制在编译单元中,但是在中声明头文件中的变量以隐藏它是不够的?

另外,我假设静态变量和非静态变量的行为不同。具体来说,internal_static_variable由包含它的文件共享(main.c的一个实例和main2.c的一个实例),但是因为我从{更改了它的值{1}}我在main.c中得到了更改后的值,两者似乎没有任何区别。

你可以解释一下吗?感谢

4 个答案:

答案 0 :(得分:1)

如果定义一个非静态全局变量,它仍然是全局变量。即使它没有在头文件中声明,它仍然可以在另一个translation unit中声明。

答案 1 :(得分:1)

当变量为extern(缺省值)时,此编译单元生成的目标文件将携带对其位置的命名引用。每当另一个目标文件与第一个目标文件链接并引用相同的命名变量但不提供其自己的定义时,链接器将使用其位置替换其使用该变量的所有实例。 CPU在执行期间处理存储器位置而不是变量名称。这就是为什么它在标题中被省略无关紧要;稍后当您链接从.c源文件创建的目标文件时,才会解析全局引用。

静态(函数外部)非常有用,因为单个库/程序可以在同一名称下拥有多个全局可访问的变量。这可以防止模块之间的名称冲突,这些模块可能为了不同的目的使用变量名称,但在他们自己的上下文中使用变量名称恰好相同。只要变量仅在当前编译单元中需要,那么您应该将其设置为静态。

答案 2 :(得分:1)

  

我不能得到的是静态的含义。我知道这限制了范围   一个变量到编译单元,但是不够   在头文件中声明一个变量来隐藏它?

这不会阻止变量被声明并因此变得可访问。这是安全和默默无闻之间的区别。通过声明static,无法通过名称从外部访问,只是不在标题中声明它,只是阻止访问那些不知道其名称和数据类型的人。更可能的情况是您的目标代码或库在别处使用,并且您会发生意外的名称冲突 - 这些错误通常很难理解。

  

我假设静态变量和非静态变量会   表现不同。具体而言,internal_static_variable会   由包含它的文件共享(main.c的一个实例和main2.c的一个实例),但是因为我从main.c改变了它的值   我在main2.c得到了改变的价值,似乎没有   两者之间存在差异。

您的代码在main.c中修改internal_static_variable;它在ext.c中仅修改 。 ext.c碰巧通过访问器函数公开internal_static_variable,在您的示例中提供了最小的保护,但作为单一的写访问点,与直接访问变量相比具有许多优势,例如: p>

  • 可以在附件中包含代码以处理无效输入,通过断言,返回错误值,中止,忽略该值以及修改变量,或强制转换为有效值例如。此类代码也可能是有条件编译的,因此它只在调试版本中执行检查。

  • 访问器函数在代码中提供了一个单点,用于放置调试器断点以捕获所有写访问。

答案 3 :(得分:1)

范围和链接

标识符有两个与此相关的属性:范围链接

范围是标识符可见的位置。您显然已经知道范围仅限于声明标识符的文件,并且可能进一步限制为块或函数(或函数原型),具体取决于声明标识符的位置和关键字(例如{{声明它时使用的1}}或static

链接是一种使标识符的不同声明引用同一对象的方法。有三种类型的链接:外部内部

如果标识符具有内部链接,则它不与其他翻译单元中的标识符链接。在一个翻译单元 1 中名为extern的对象无法通过另一个翻译单元中的名称访问。 2

如果标识符具有外部链接,则可以通过声明具有相同名称的标识符以及外部链接在另一个转换单元中访问它。当程序链接在一起时,链接器会解析具有外部链接的标识符,以便它们引用相同的存储。

外部链接问题

您可以省略foo并将您的标识符保留为外部链接。只要您是编写程序的唯一人,就可以避免出现问题。但这并不整洁;它会留下一些晃来晃去的东西,这可能会导致问题。

如果您正在编写要在其他程序中使用的例程,那么将私有标识符与外部链接放在一起可能会成为问题,特别是如果它们具有简单,通用的名称。在自己的代码中使用例程的人可能会巧合使用相同的名称,然后您的两个标识符将链接到同一个对象,即使您需要它们不同。

这也可以故意发生。如果您编写一个流行的软件包并将私有名称保留为外部链接,则该软件包的某些用户可能会探索存在的名称并尝试使用它们。这可能导致人们创建软件,利用软件中应该是私有的东西。然后,您无法开发新版本的软件,在不破坏现有软件的情况下更改私有部件。这成了一个商业问题。您可能需要在软件包中实现新算法,但您不想破坏客户的现有源代码。最初使用static声明名称可以避免这种情况。

声明如何影响链接

在文件范围内使用static声明标识符时,它具有内部链接。除此之外,标识符与之关联的规则有点复杂,部分原因在于C语言的开发历史:

  • 如果没有事先声明可见,则使用static声明标识符会为其提供外部链接。

  • 如果有可见的先前声明,extern将使用与上一个声明中相同的链接保留标识符。

  • 在没有externextern的文件范围内声明函数或对象会为标识符提供外部链接。

  • 即使使用了static,没有extern的块范围内的对象声明也没有链接。

  • 功能参数没有联系。

  • 非对象或函数(例如类型定义)的标识符没有链接。

在一个翻译单元中,具有内部链接的标识符的每个声明表示相同的对象或功能。具有无链接的标识符的每个声明表示唯一实体。 (此段落是C 2011 [N1570] 6.2.2的直接引用,此答案中的其他信息也来自那里。)

脚注

1 翻译单元是由所有static指令产生的组合源代码。我使用技术术语“翻译单元”而不是“源文件”,因为可以使用#include指令在另一个源文件中访问一个源文件中名为foo的对象。

2 如果将其地址从一个函数传递给另一个函数,则仍可以使用指针在另一个转换单元中访问具有内部链接的对象。