"const static"在C和C++中意味着什么?

c0m*_*0m4 111 c c++

const static int foo = 42;
Run Code Online (Sandbox Code Playgroud)

我在StackOverflow上的一些代码中看到了这个,我无法弄清楚它是做什么的.然后我在其他论坛上看到了一些困惑的答案.我最好的猜测是它在C中用来隐藏foo其他模块的常量.它是否正确?如果是这样,为什么有人会在C++上下文中使用它,你可以做到这一点private

Mot*_*tti 199

很多人给出了基本答案,但没有人指出,在C++中const默认staticnamespace水平(有的给出了错误信息).请参阅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意味着它不会更改.

  • 在函数级别:static不是const还原,它们可以表现不同`const int*foo(int x){const int b = x; return&b};`vs`const int*foo(int x){static const int b = x; return&b};` (2认同)
  • 问题是关于 C 和 C++,所以你应该包括一个关于 `const` 的注释,只在后者中暗示 `static`。 (2认同)

Chr*_*uin 104

它在C和C++中都有用.

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

这就是C如何处理这些变量(或C++如何处理命名空间变量).在C++中,标记的成员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)

  • +1表示静态const与命名空间范围中的const相同. (2认同)
  • @Mikhail:你说的没错.假设标头可以包含在多个TU中,因此单独讨论它是有用的. (2认同)

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)


Kev*_*vin 8

它缺少'int'.它应该是:

const static int foo = 42;
Run Code Online (Sandbox Code Playgroud)

在C和C++中,它声明了一个整数常量,其本地文件范围为42.

为什么42?如果你还不知道(很难相信你不知道),那就是对生命,宇宙和万物回答.


Ale*_*ekh 5

根据 C99/GNU99 规范:

  • static

    • 是存储类说明符

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

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

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

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

      • MyObj const * myVar; - 指向 const 限定对象类型的非限定指针

      • MyObj * const myVar; - 指向非限定对象类型的 const 限定指针

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

      • const MyObj * myVar; - 指向 const 限定对象类型的非限定指针

因此:

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

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


Cir*_*四事件 5

C++17inline变量

如果您在 Google 上搜索“C++ const static”,那么您很可能真正想要使用的是C++17 inline variables

这个很棒的 C++17 特性允许我们:

主程序

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_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 &notmain_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)

GitHub 上游.

另请参阅:内联变量如何工作?

内联变量的 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 nmu

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

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

C++ 17 之前: extern const

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

缺点inline是:

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

主程序

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_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 &notmain_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)

GitHub 上游.

Pre-C++17 header only 替代方案

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

一个constexpr函数,因为constexpr暗示inlineinline 允许(强制)定义出现在每个翻译单元上

constexpr int shared_inline_constexpr() { return 42; }
Run Code Online (Sandbox Code Playgroud)

我敢打赌,任何体面的编译器都会内联调用。

您还可以使用 aconstconstexprstatic 变量,如下所示:

#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:有什么方法可以完全内联变量,而根本不使用任何内存?

就像预处理器所做的一样。

这需要以某种方式:

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

有关的:

在 Ubuntu 18.10、GCC 8.2.0 中测试。


归档时间:

查看次数:

177741 次

最近记录:

6 年,2 月 前