const static int foo = 42;
Run Code Online (Sandbox Code Playgroud)
我在StackOverflow上的一些代码中看到了这个,我无法弄清楚它是做什么的.然后我在其他论坛上看到了一些困惑的答案.我最好的猜测是它在C中用来隐藏foo其他模块的常量.它是否正确?如果是这样,为什么有人会在C++上下文中使用它,你可以做到这一点private?
Mot*_*tti 199
很多人给出了基本答案,但没有人指出,在C++中const默认static的namespace水平(有的给出了错误信息).请参阅C++ 98标准第3.5.3节.
首先是一些背景:
翻译单元:预处理器(递归地)之后的源文件包括其所有包含文件.
静态链接:符号仅在其翻译单元中可用.
外部链接:其他翻译单元可以使用符号.
namespace水平这包括全局命名空间aka全局变量.
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
Run Code Online (Sandbox Code Playgroud)
static表示在函数调用之间保持该值.
函数static变量的语义类似于全局变量,因为它们驻留在程序的数据段(而不是堆栈或堆)中,有关变量生命周期的更多详细信息,请参阅此问题static.
class水平static表示该值在类的所有实例之间共享,并且const意味着它不会更改.
Ric*_*den 44
这行代码实际上可以出现在几个不同的上下文中,并且尽管它的行为大致相同,但存在小的差异.
// foo.h
static const int i = 0;
Run Code Online (Sandbox Code Playgroud)
' i'将在包含标题的每个翻译单元中显示.但是,除非您实际使用对象的地址(例如.' &i'),否则我很确定编译器会将' i'简单地视为类型安全0.如果两个以上的翻译单元采用' &i',则每个翻译单元的地址将不同.
// foo.cc
static const int i = 0;
Run Code Online (Sandbox Code Playgroud)
' i'具有内部联系,因此不能从该翻译单元外部引用.但是,除非您使用其地址,否则它很可能被视为类型安全0.
值得指出的一点是,以下声明:
const int i1 = 0;
Run Code Online (Sandbox Code Playgroud)
是完全相同一样static const int i = 0.声明为const和未显式声明的名称空间中的变量extern是隐式静态的.如果您考虑这一点,C++委员会的目的是允许const在头文件中声明变量而不总是需要static关键字以避免破坏ODR.
class A {
public:
static const int i = 0;
};
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,标准明确指定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
Run Code Online (Sandbox Code Playgroud)
Fer*_*cio 22
这是一个小空间优化.
当你说
const int foo = 42;
Run Code Online (Sandbox Code Playgroud)
您没有定义常量,而是创建只读变量.编译器足够智能,只要看到foo就会使用42,但它也会为初始化数据区域分配空间.这样做是因为,如所定义的,foo具有外部链接.另一个编译单位可以说:
extern const int foo;
获取其价值的访问权限.这不是一个好习惯,因为编译单元不知道foo的价值是什么.它只知道它是一个const int,并且必须在使用它时从内存重新加载它.
现在,通过声明它是静态的:
static const int foo = 42;
Run Code Online (Sandbox Code Playgroud)
编译器可以进行通常的优化,但它也可以说"嘿,这个编译单元之外的任何人都看不到foo,我知道它总是42,所以不需要为它分配任何空间."
我还应该注意,在C++中,防止名称转义当前编译单元的首选方法是使用匿名命名空间:
namespace {
const int foo = 42; // same as static definition above
}
Run Code Online (Sandbox Code Playgroud)
它缺少'int'.它应该是:
const static int foo = 42;
Run Code Online (Sandbox Code Playgroud)
在C和C++中,它声明了一个整数常量,其本地文件范围为42.
为什么42?如果你还不知道(很难相信你不知道),那就是对生命,宇宙和万物的回答.
根据 C99/GNU99 规范:
static
是存储类说明符
默认情况下文件级范围的对象具有外部链接
const
是类型限定符(是类型的一部分)
应用于直接左实例的关键字 - 即
MyObj const * myVar; - 指向 const 限定对象类型的非限定指针
MyObj * const myVar; - 指向非限定对象类型的 const 限定指针
最左边的用法 - 应用于对象类型,而不是变量
const MyObj * myVar; - 指向 const 限定对象类型的非限定指针因此:
static NSString * const myVar; - 指向具有内部链接的不可变字符串的常量指针。
缺少static关键字将使变量名称成为全局变量,并可能导致应用程序中的名称冲突。
C++17inline变量
如果您在 Google 上搜索“C++ const static”,那么您很可能真正想要使用的是C++17 inline variables。
这个很棒的 C++17 特性允许我们:
constexpr:如何声明 constexpr extern?主程序
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
Run Code Online (Sandbox Code Playgroud)
不是main.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
Run Code Online (Sandbox Code Playgroud)
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
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
Run Code Online (Sandbox Code Playgroud)
另请参阅:内联变量如何工作?
内联变量的 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
Run Code Online (Sandbox Code Playgroud)
其中包含:
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
Run Code Online (Sandbox Code Playgroud)
并man nm说u:
“u”符号是唯一的全局符号。这是标准 ELF 符号绑定集的 GNU 扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个具有此名称和类型的符号在使用。
所以我们看到有一个专门的 ELF 扩展。
C++ 17 之前: extern const
在 C++ 17 之前和在 C 中,我们可以使用 实现非常相似的效果extern const,这将导致使用单个内存位置。
缺点inline是:
constexpr用这种技术制作变量,只inline允许:如何声明 constexpr extern?主程序
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
Run Code Online (Sandbox Code Playgroud)
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
Run Code Online (Sandbox Code Playgroud)
不是main.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
Run Code Online (Sandbox Code Playgroud)
Pre-C++17 header only 替代方案
这些不如extern解决方案好,但它们可以工作并且只占用一个内存位置:
一个constexpr函数,因为constexpr暗示inline并inline 允许(强制)定义出现在每个翻译单元上:
constexpr int shared_inline_constexpr() { return 42; }
Run Code Online (Sandbox Code Playgroud)
我敢打赌,任何体面的编译器都会内联调用。
您还可以使用 aconst或constexprstatic 变量,如下所示:
#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;
}
Run Code Online (Sandbox Code Playgroud)
但是您不能执行诸如获取其地址之类的操作,否则它会被 odr 使用,另请参阅:定义 constexpr 静态数据成员
C
在 C 中,情况与 C++ 17 之前的 C++ 相同,我上传了一个示例:C 中的“静态”是什么意思?
唯一的区别是,在 C++ 中,对全局变量const隐含static,但在 C 中则不然:`static const` 与 `const` 的 C++ 语义
有什么方法可以完全内联它?
TODO:有什么方法可以完全内联变量,而根本不使用任何内存?
就像预处理器所做的一样。
这需要以某种方式:
有关的:
在 Ubuntu 18.10、GCC 8.2.0 中测试。
| 归档时间: |
|
| 查看次数: |
177741 次 |
| 最近记录: |