Answers:
它在C和C ++中都有使用。
如您所料,该static
部分将其范围限制为该编译单元。它还提供了静态初始化。const
只是告诉编译器不要让任何人对其进行修改。根据体系结构,此变量可以放在data或bss段中,并且可以在内存中标记为只读。
C如何处理这些变量(或C ++如何处理命名空间变量)。在C ++中,标记的成员static
由给定类的所有实例共享。无论是否私有,都不会影响一个变量被多个实例共享的事实。如果const
有任何代码尝试修改该代码,则在那里发出警告。
如果严格将其设为私有,则该类的每个实例都会获得自己的版本(尽管有优化程序)。
很多人给出了基本答案,但没有人指出,在C ++中const
默认static
的namespace
水平(有的给出了错误信息)。参见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
表示其不变。
const int *foo(int x) {const int b=x;return &b};
与const int *foo(int x) {static const int b=x;return &b};
const
仅暗示static
于后者的注释。
const
宣言也暗示static
着吗?例如,如果您const
放弃并修改了值,是否会修改所有值?
const
并不意味着在函数级别上是静态的,那将是并发的噩梦(const!=常量表达式),函数级别上的所有内容都是隐式的auto
。由于这个问题也被标记为[c],因此我应该提到C中const int
隐含extern
了一个全局级别。但是,这里的规则完美地描述了C ++。
static
表示该变量是静态持续时间(仅存在一个副本,从程序的开始一直持续到其结束),并且如果没有其他指定,则具有内部/静态链接(由函数的覆盖)局部静态变量的链接,或静态成员的类的链接)。主要区别在于在每种情况下static
有效的含义。
该行代码实际上可以出现在几个不同的上下文中,尽管其行为大致相同,但差异很小。
// 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具有外部链接。另一个编译单元可以说:
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和C ++中,它声明一个本地文件范围为42的整数常量。
对于所有出色的答案,我想添加一个小细节:
如果您编写插件(例如,要由CAD系统加载的DLL或.so库),则static是可以避免这样的名称冲突的救生器:
更糟糕的是:步骤3的行为可能会有所不同,具体取决于编译器优化,插件加载机制等。
我曾经在两个插件中使用两个辅助函数(名称相同,行为不同)遇到过此问题。声明它们为static可解决问题。
根据C99 / GNU99规范:
static
是存储类说明符
默认情况下,文件级别范围的对象具有外部链接
const
是类型限定符(是类型的一部分)
关键字应用于立即左实例-即
MyObj const * myVar;
-指向const限定对象类型的非限定指针
MyObj * const myVar;
-const合格的指向不合格对象类型的指针
最左边的用法-应用于对象类型,不是变量
const MyObj * myVar;
-指向const限定对象类型的非限定指针从而:
static NSString * const myVar;
-带有内部链接的不可变字符串的常量指针。
缺少static
关键字将使变量名变为全局名,并可能导致应用程序中的名称冲突。
C ++ 17 inline
变量
如果您用Google搜索“ C ++ const static”,那么您真正想使用的是C ++ 17内联变量。
这个很棒的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://zh.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
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;
}
但是您不能做诸如获取其地址之类的事情,否则它会变得奇怪,请参见:定义constexpr静态数据成员
C
在C中,情况与C ++ pre C ++ 17相同,我在以下位置上载了一个示例:“静态”在C中是什么意思?
唯一的区别是在C ++中const
隐含static
全局变量,但在C中却不隐含:“静态常量”与“常量”的C ++语义
有什么办法可以完全内联吗?
TODO:有什么方法可以完全内联变量,而无需使用任何内存?
就像预处理器一样。
这将需要某种方式:
有关:
在Ubuntu 18.10,GCC 8.2.0中进行了测试。
这是全局常量,仅在编译模块(.cpp文件)中可见/可访问。为此不建议使用static的BTW。更好地使用匿名名称空间和枚举:
namespace
{
enum
{
foo = 42
};
}
enum
在这种情况下它有什么好处。关心详细吗?这些enums
通常仅用于防止编译器为该值分配任何空间(尽管现代编译器不需要为此使用enum
hack)并防止创建指向该值的指针。
将其设为私有仍然意味着它会出现在标题中。我倾向于使用“最弱”的方法。请参阅Scott Meyers撰写的经典文章:http : //www.ddj.com/cpp/184401197(它与功能有关,但也可以在此处应用)。