What does 'const static' mean in C and C++?

const static int foo = 42;

I saw this in some code here on StackOverflow and I couldn't figure out what it does. Then I saw some confused answers on other forums. My best guess is that it's used in C to hide the constant foo from other modules. Is this correct? If so, why would anyone use it in a C++ context where you can just make it private?

239322 次浏览

它少了一个“ int”,应该是:

const static int foo = 42;

在 C 和 C + + 中,它声明了一个本地文件作用域为值42的整数常量。

为什么是42?如果你不知道(很难相信你不知道) ,这是一个参考 生命、宇宙和一切的答案

它在 C 和 C + + 中都有用途。

正如您猜测的那样,static部分将其范围限制为该 编译单元。它还提供了静态初始化。const只是告诉编译器不要让任何人修改它。根据架构的不同,这个变量可以放在数据段中,也可以放在 bss 段中,并且可能放在标记为只读的内存中。

这就是 C 如何处理这些变量(或者 C + + 如何处理名称空间变量)。在 C + + 中,标记为 static的成员由给定类的所有实例共享。它是否为私有并不影响一个变量被多个实例共享的事实。有 const在那里将警告您,如果任何代码将尝试修改。

如果它是严格私有的,那么类的每个实例将获得自己的版本(尽管有优化器)。

是的,它向其他模块隐藏模块中的变量。在 C + + 中,当我不想/需要更改。H 文件,该文件将触发不必要的重新生成其他文件。此外,我把静电放在第一位:

static const int foo = 42;

而且,根据它的用途,编译器甚至不会为它分配存储空间,而只是简单地“内联”使用它的值。如果没有静态,编译器就不能假设它没有在其他地方使用,也不能内联。

这是仅在编译模块(。Cpp 档案)。BTW 为此目的使用静态是不推荐的。最好使用匿名名称空间和枚举:

namespace
{
enum
{
foo = 42
};
}

将其设为私有仍然意味着它出现在标题中。我喜欢用“最弱”这个词。参见 Scott Meyers 的这篇经典文章: http://www.ddj.com/cpp/184401197(它是关于函数的,但也可以在这里应用)。

在 C + + 中,

static const int foo = 42;

是定义和使用常量的首选方法

#define foo 42

因为它不会破坏类型安全系统。

这行代码实际上可以出现在几个不同的上下文中,尽管它们的行为大致相同,但是存在一些小的差异。

命名空间范围

// foo.h
static const int i = 0;

i’将在包括标题的每个翻译单元中可见。但是,除非您实际使用对象的地址(例如。“ &i”) ,我很确定编译器将把“ i”简单地当作类型安全的 0。如果再有两个翻译单元使用“ &i”,那么每个翻译单元的地址将不同。

// foo.cc
static const int i = 0;

i”有内部联系,因此不能从这个翻译单元的外部引用。但是,除非您使用它的地址,否则它很可能被视为类型安全的 0

值得指出的是,以下声明:

const int i1 = 0;

没错static const int i = 0是一样的。在用 const声明而没有用 extern显式声明的命名空间中的变量是隐式静态的。如果您考虑到这一点,C + + 委员会的意图是允许在头文件中声明 const变量,而无需始终使用 static关键字来避免破坏 ODR。

类范围

class A {
public:
static const int i = 0;
};

在上面的例子中,标准明确指定如果不需要地址,则不需要定义“ i”。换句话说,如果你只使用‘ i’作为类型安全的0,那么编译器将不会定义它。类和名称空间版本之间的一个区别是‘ i’的地址(如果在两个或更多的翻译单元中使用)对于类成员是相同的。在使用该地址时,必须对其进行定义:

// a.h
class A {
public:
static const int i = 0;
};


// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address

这是一个很小的空间优化。

当你说

const int foo = 42;

您没有定义常量,而是创建了一个只读变量。编译器足够聪明,每当看到 foo 时都会使用42,但是它也会在初始化的数据区域中为其分配空间。之所以这样做,是因为按照定义,foo 具有外部联系。另一个汇编单位可以说:

外部常量;

获取它的价值。这不是一个好的实践,因为那个编译单元不知道 foo 的价值是什么。它只知道它是一个常量 int,并且只要使用它,就必须从内存中重新加载该值。

现在,通过声明它是静态的:

static const int foo = 42;

编译器可以进行通常的优化,但它也可以说: “嘿,这个编译单元以外的人都看不到 foo,我知道它总是42,所以没有必要为它分配任何空间。”

我还应该指出,在 C + + 中,防止名称转义当前编译单元的首选方法是使用匿名名称空间:

namespace {
const int foo = 42; // same as static definition above
}

很多人给出了基本的答案,但是没有人指出,在 C + + 中,constnamespace级默认为 static(有些人给出了错误的信息)。请参阅 C + + 98标准部分3.5.3。

首先介绍一些背景知识:

翻译单元: 预处理器(递归地)包含所有包含文件后的源文件。

静态链接: 符号仅在其翻译单元内可用。

外部链接: 符号可从其他翻译单位获得。

namespace水平

这包括全局名称空间,即全局变量

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

在功能层面

static表示在函数调用之间维护该值。
函数 static变量的语义类似于全局变量,因为它们位于 程序的数据段(而不是堆栈或堆)中,有关 static变量生命周期的更多细节,请参见 这个问题

class水平

static表示该值在类的所有实例之间共享,而 const表示该值不变。

对于所有精彩的回答,我想补充一个小细节:

如果您编写插件(例如 DLL 或。这样的库就可以被 CAD 系统加载) ,那么 静电干扰就是一个避免这种名称冲突的救命稻草:

  1. CAD 系统加载一个插件 A,其中有一个“ const int foo = 42;”。
  2. 系统加载一个插件 B,其中包含“ const int foo = 23;”。
  3. 因此,插件 B 将使用值42作为 foo,因为插件加载程序将意识到,已经有一个具有外部链接的“ foo”。

更糟糕的是: 根据编译器最佳化、插件加载机制等,第3步可能会有不同的表现。

我曾经在两个插件中的两个 helper 函数(相同的名称,不同的行为)中遇到过这个问题。声明它们是静态的就解决了问题。

根据 C99/GNU99规范:

  • static

    • 是存储类说明符

    • 默认情况下,文件级别范围的对象具有 外部链接

    • 具有静态说明符的文件级范围的对象具有 内部链接
  • const

    • 是类型限定符(是类型的一部分)

    • 关键字应用于直接的左实例-即。

      • 指向 const 限定对象类型的非限定指针

      • 指向非限定对象类型的限定指针

    • 最左边的用法-应用于对象类型,而不是变量

      • 指向 const 限定对象类型的非限定指针

因此:

static NSString * const myVar;-指向具有内部链接的不可变字符串的常量指针。

缺少 static关键字将使变量名成为全局变量,并可能导致应用程序内部的名称冲突。

C + + 17个 inline变量

如果你用 Google 搜索“ C + + const static”,那么很可能你真正想用的是 C + + 17个内联变量

这个了不起的 C + + 17特性允许我们:

Main.cpp

#include <cassert>


#include "notmain.hpp"


int main() {
// Both files see the same memory address.
assert(&notmain_i == notmain_func());
assert(notmain_i == 42);
}

不是 Main.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP


inline constexpr int notmain_i = 42;


const int* notmain_func();


#endif

不是 main.cpp

#include "notmain.hpp"


const int* notmain_func() {
return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub 上游。

参见: 内联变量是如何工作的?

内联变量的 C + + 标准

C + + 标准保证地址是相同的。“ a href =”https://github.com/cplusplus/draft/blob/master/paper/N4659.pdf”rel = “ noReferrer”> C + + 17 N4659标准草案 10.1.6“内联说明符”:

具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

https://en.cppreference.com/w/cpp/language/inline解释说,如果 static没有给出,那么它有外部连接。

GCC 内联变量实现

我们可以观察它是如何通过以下方式实施的:

nm main.o notmain.o

其中包括:

main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i


notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

man nmu说:

“ u”符号是一个独特的全球性符号。这是对 ELF 符号绑定标准集的 GNU 扩展。对于这样一个符号,动态链接器将确保在整个过程中 只有一个符号使用这个名称和类型。

所以我们看到有一个专用的 ELF 扩展。

Pre-C + + 17: extern const

在 C + + 17之前,在 C 中,我们可以使用 extern const实现非常类似的效果,这将导致使用单个内存位置。

inline的缺点是:

  • 使用这种技术不可能使变量 constexpr,只有 inline允许: 如何声明 conexpr extern?
  • 它不那么优雅,因为必须在头文件和 cpp 文件中分别声明和定义变量

Main.cpp

#include <cassert>


#include "notmain.hpp"


int main() {
// Both files see the same memory address.
assert(&notmain_i == notmain_func());
assert(notmain_i == 42);
}

不是 main.cpp

#include "notmain.hpp"


const int notmain_i = 42;


const int* notmain_func() {
return &notmain_i;
}

不是 Main.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP


extern const int notmain_i;


const int* notmain_func();


#endif

GitHub 上游。

Pre-C + + 17标头的唯一选择

这些都不如 extern解决方案,但它们可以工作,而且只占用一个内存位置:

一个 constexpr函数,因为 ABC0意味着 inlineinline 允许(强制)定义出现在每个翻译单元上:

constexpr int shared_inline_constexpr() { return 42; }

我打赌任何一个好的编译器都会内联调用。

还可以使用 constconstexpr静态变量,如下所示:

#include <iostream>


struct MyClass {
static constexpr int i = 42;
};


int main() {
std::cout << MyClass::i << std::endl;
// undefined reference to `MyClass::i'
//std::cout << &MyClass::i << std::endl;
}

但是你不能做这样的事情,比如记录它的地址,否则它会变得很奇怪,参见: 定义 conexpr 静态数据成员

C

在 C 中,情况与 C + + pre C + + 17相同,我在 “ static”在 C 语言中是什么意思?上传了一个示例

唯一的区别是,在 C + + 中,const对于全局变量意味着 static,但是在 C: “静态 const”与“ const”的 C + + 语义中没有

有什么办法能把它完全嵌入吗?

TODO: 有没有什么办法可以完全内联这个变量,而不需要使用任何内存?

就像预处理器一样。

这需要以某种方式:

  • 禁止或检测变量的地址是否被采用
  • 将该信息添加到 ELF 对象文件中,并让 LTO 对其进行优化

相关阅读:

在 Ubuntu 18.10,GCC 8.2.0中测试。