const static int foo = 42;
我在StackOverflow的一些代码中看到了这个,但我无法弄清它的作用。然后我在其他论坛上看到了一些困惑的回答。我最好的猜测是它用于C中将常量foo
隐藏在其他模块中。如果是这样的话,在C++环境下为什么有人要使用它而不是直接将其设为private
?
const
默认为namespace
级别的static
(有些人给出了错误信息)。请查看C++98标准第3.5.3节。首先介绍一些背景知识:翻译单元:经过预处理器(递归)包含所有include文件后的源文件。静态链接:符号仅在其翻译单元中可用。外部链接:符号可以从其他翻译单元中使用。在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
意味着它不会改变。
它在C和C++中都有用途。
正如你猜测的那样,static
部分将其范围限制在编译单元内。它还提供了静态初始化。 const
只是告诉编译器不允许任何人修改它。该变量可能被放置在数据或bss段中,具体取决于架构,并且可能在内存中标记为只读。
所有这些都是C如何处理这些变量(或者C++如何处理命名空间变量)。在C++中,标记为static
的成员由给定类的所有实例共享。无论它是私有的还是公共的,都不影响一个变量被多个实例共享的事实。如果在这上面加上const
,则会在任何代码尝试修改它时发出警告。
如果它严格是私有的,则每个类实例都将得到自己的版本(优化器除外)。
那行代码实际上可以出现在几个不同的上下文中,虽然它的行为大致相同,但存在一些小差异。
// 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;
在命名空间中用const
声明的变量,且没有显式使用extern
关键字,其实和static const int i = 0
是完全相同的。这种情况下,const
变量会被隐式声明为静态变量。如果你仔细想一下,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具有外部链接性。另一个编译单元可以说:
extern const int foo;
以获得对其值的访问。这不是一个好习惯,因为那个编译单元不知道foo的值。它只知道它是一个const int,并且每次使用时必须从内存重新加载值。
现在,通过声明它是静态的:
static const int foo = 42;
编译器可以进行常规的优化,但它也可以说:“嘿,这个编译单元之外的人看不到foo,我知道它总是42,所以没有必要为它分配任何空间。”
另外需要注意的是,在C++中,防止名称从当前编译单元逃逸的首选方法是使用匿名命名空间:
namespace {
const int foo = 42; // same as static definition above
}
缺少了一个 'int'。应该是:
const static int foo = 42;
C++17 inline
变量
如果你在谷歌上搜索的是"C++ const static",那么你很可能真正想要使用的是C++17 inline 变量。
这个厉害的 C++17 特性允许我们:
constexpr
:如何声明 constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_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
另请参阅:内联变量如何工作?
C++标准中的内联变量
C++标准保证地址相同。C++17 N4659标准草案 10.1.6“内联说明符”:
6 具有外部链接的内联函数或变量在所有翻译单元中都应具有相同的地址。
cppreference 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 nm
介绍了关于u
的信息:
"u"这个符号是一个独特的全局符号。这是标准ELF符号绑定集合的GNU扩展。对于这样的符号,动态链接器将确保在整个进程中只使用一个具有此名称和类型的符号。
因此,我们可以看到有一个专门的ELF扩展。
C++ 17之前: extern const
在C++ 17之前以及在C语言中,我们可以通过extern const
实现非常相似的效果,这将导致只使用一个内存位置。
与inline
相比的缺点是:
constexpr
,只有inline
允许这样做:如何声明constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
Pre-C++17仅头文件替代方案
这些方案不如extern
方案好,但它们可行并且只占用单个内存位置:
使用constexpr
函数,因为constexpr
意味着inline
和inline
允许(强制)定义出现在每个翻译单元上:
constexpr int shared_inline_constexpr() { return 42; }
我敢打赌,任何一个好的编译器都会内联这个调用。
你也可以使用一个 const
或 constexpr
静态变量,例如:
#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;
}
但你不能像获取它的地址这样做,否则它就成为了odr-used,参见:定义constexpr静态数据成员
C语言
在C语言中,情况与C++ 17之前的情况相同,我上传了一个示例:C中"static"是什么意思?
唯一的区别是,在C++中,对于全局变量,const
意味着static
,但在C中不是这样的:C++中`static const`与`const`的语义
有没有完全内联它的方法?
待办事项:有没有完全内联变量的方法,而不使用任何内存?
就像预处理器所做的那样。
这需要以某种方式:
相关内容:
在Ubuntu 18.10,GCC 8.2.0中测试。
在C++中,
static const int foo = 42;
使用常量的首选方式是定义和使用常量。即使用此方法而不是
#define foo 42
因为它不会破坏类型安全系统。
是的,它可以隐藏模块中的变量,使其对其他模块不可见。在C++中,当我不想/不需要更改.h文件以触发其他文件的不必要重建时,我会使用它。此外,我首先将static放在前面:
static const int foo = 42;
此外,根据其用途,编译器甚至不会为其分配存储空间,而是在使用它的地方直接“内联”该值。没有static关键字,编译器无法假设它不会在其他地方使用,并且无法内联。
const int *foo(int x) {const int b=x;return &b};
和const int *foo(int x) {static const int b=x;return &b};
。 - Hanczarconst
仅在后者中意味着static
的注释。 - Nikolai Ruheconst
声明也意味着那里有static
?换句话说,如果您取消const
并修改该值,所有值都将被修改吗? - Cookieconst
在函数级别上并不意味着静态,否则这将是一个并发的噩梦(const != 常量表达式),在函数级别上的所有内容都隐式地是auto
。由于这个问题也被标记为 [c],我应该提到,在 C 中,全局级别的const int
隐式地是extern
。你在这里描述的规则完美地描述了 C++。 - Ryan Hainingstatic
表示变量具有静态持续时间(仅存在一个副本,从程序的开始到结束都将存在),并且如果未另行指定,则具有内部/静态链接性(对于局部静态变量,函数的链接性会覆盖此链接性,对于静态成员,则为类的链接性)。主要区别在于,在每个static
有效的情况下,这意味着什么。 - Justin Time - Reinstate Monica