“常量静态”在C和C ++中是什么意思?


117
const static int foo = 42;

我在StackOverflow上的某些代码中看到了这一点,但无法弄清楚它的作用。然后我在其他论坛上看到了一些困惑的答案。我最好的猜测是,它在C语言中用于隐藏foo其他模块中的常量。这样对吗?如果是这样,为什么有人会在可以制作它的C ++上下文中使用它private呢?

Answers:


113

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

如您所料,该static部分将其范围限制为该编译单元。它还提供了静态初始化。const只是告诉编译器不要让任何人对其进行修改。根据体系结构,此变量可以放在data或bss段中,并且可以在内存中标记为只读。

C如何处理这些变量(或C ++如何处理命名空间变量)。在C ++中,标记的成员static由给定类的所有实例共享。无论是否私有,都不会影响一个变量被多个实例共享的事实。如果const有任何代码尝试修改该代码,则在那里发出警告。

如果严格将其设为私有,则该类的每个实例都会获得自己的版本(尽管有优化程序)。


1
最初的示例是在谈论“私有变量”。因此,这是一个成员,静态对链接没有影响。您应该删除“静态部分将其范围限制为该文件”。
理查德·科登

“特殊部分”被称为数据段,它与所有其他全局变量(例如显式“字符串”和全局数组)共享。这与代码段相反。
spoulson

@Richard-是什么让您认为它是课程的成员?问题中没有什么是事实。如果它是类的成员,那么您是正确的,但是如果它只是在全局范围内声明的变量,那么Chris是正确的。
Graeme Perrow

1
原始张贴者提到私有是可能的更好解决方案,但不是原始问题。
克里斯·阿金

@Graeme,好的,因此它不是“绝对”的成员-但是,此答案是仅适用于名称空间成员的语句,而这些语句对于成员变量来说是错误的。考虑到投票数量,该错误可能会使对这种语言不太熟悉的人感到困惑-应该对其进行修复。
理查德·科登

212

很多人给出了基本答案,但没有人指出,在C ++中const默认staticnamespace水平(有的给出了错误信息)。参见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表示其不变。


2
在功能级别上: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};
Hanczar 2012年

1
问题是关于C和C ++的,所以您应该包括一个const仅暗示static于后者的注释。
Nikolai Ruhe 2013年

@Motti:好答案。您能否在功能级别上解释什么使冗余成为什么?您是在说const宣言也暗示static着吗?例如,如果您const放弃并修改了值,是否会修改所有值?
Cookie

1
@Motti const并不意味着在函数级别上是静态的,那将是并发的噩梦(const!=常量表达式),函数级别上的所有内容都是隐式的auto。由于这个问题也被标记为[c],因此我应该提到C中const int隐含extern了一个全局级别。但是,这里的规则完美地描述了C ++。
瑞安·海宁

1
在C ++中,在所有这三种情况下,都static表示该变量是静态持续时间(仅存在一个副本,从程序的开始一直持续到其结束),并且如果没有其他指定,则具有内部/静态链接(由函数的覆盖)局部静态变量的链接,或静态成员的类的链接)。主要区别在于在每种情况下static有效的含义。
贾斯汀时间-恢复莫妮卡

45

该行代码实际上可以出现在几个不同的上下文中,尽管其行为大致相同,但差异很小。

命名空间范围

// 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

2
+1指出静态const与名称空间范围内的const相同。
Plumenator 2011年

实际上,放置在“ foo.h”或“ foo.cc”之间没有区别,因为在编译翻译单元时仅包含.h。
米哈伊尔(Mikhail)

2
@米哈伊尔:你是对的。假设可以将标头包含在多个TU中,因此单独讨论它很有用。
理查德·科登

24

这是一个小空间优化。

当你说

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
}

1
,u提及时未使用静态“它还将在初始化的数据区域中为其分配空间”。并通过使用静态的“不需要为其分配任何空间”。(从编译器从中选择val呢?)可以用堆和堆栈的方式解释变量的存储位置。如果我在解释它,请更正我错了。
Nihar 2015年

@ N.Nihar-静态数据区域是固定大小的内存块,其中包含所有具有静态链接的数据。它是通过将程序加载到内存的过程来“分配”的。它不是堆栈或堆的一部分。
Ferruccio 2015年

如果我有一个函数返回指向foo的指针怎么办?这会破坏优化吗?
nw。

@nw:是的,必须这样做。
Ferruccio

8

它缺少一个“ int”。它应该是:

const static int foo = 42;

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

为什么是42?如果您还不知道(很难相信您还不知道),那么这就是对生命,宇宙和一切答案的参考


谢谢。。。。。。。。。。。。。。哈哈
Inisheer

这证明了宇宙是由13个手指组成的(问题和答案实际上在基数13中匹配)。
paxdiablo

它的老鼠。每只脚上有3个脚趾,再加上一条尾巴,您的
脚底为13。– KeithB

您实际上不需要在声明中使用“ int”,尽管将其写出来绝对是一种好习惯。默认情况下,C始终采用“ int”类型。试试吧!
迅速

“本地文件范围的值为42”?还是整个编译单元?
aniliitb10 2015年

4

在C ++中,

static const int foo = 42;

是定义和使用常量的首选方式。即使用此而不是

#define foo 42

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


4

对于所有出色的答案,我想添加一个小细节:

如果您编写插件(例如,要由CAD系统加载的DLL或.so库),则static是可以避免这样的名称冲突的救生器:

  1. CAD系统将加载一个插件A,该插件具有“ const int foo = 42;”。在里面。
  2. 系统加载了插件B,该插件具有“ const int foo = 23;”。在里面。
  3. 结果,插件B将对foo使用值42,因为插件加载程序将意识到,已经存在带有外部链接的“ foo”。

更糟糕的是:步骤3的行为可能会有所不同,具体取决于编译器优化,插件加载机制等。

我曾经在两个插件中使用两个辅助函数(名称相同,行为不同)遇到过此问题。声明它们为static可解决问题。


两个插件之间的名称冲突似乎有些奇怪,这导致我检查了许多DLL的链接映射,这些DLL将m_hDfltHeap定义为具有外部链接的句柄。果然,全世界都可以看到和使用,在链接映射中以_m_hDfltHeap列出。我已经忘记了所有关于这个事实的东西。
David A. Gray,

4

根据C99 / GNU99规范:

  • static

    • 是存储类说明符

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

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

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

    • 关键字应用于立即左实例-即

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

      • MyObj * const myVar; -const合格的指向不合格对象类型的指针

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

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

从而:

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

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


4

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);
}

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 &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 ++标准保证地址相同。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 nmu

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

因此我们看到有专门的ELF扩展程序。

C ++ 17之前的版本: extern const

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

缺点inline是:

  • constexpr使用这种技术不可能创建变量,仅inline允许这样做:如何声明constexpr 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);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

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

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub上游

C ++ 17之前的标头仅替代方法

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

一个constexpr函数,因为constexpr隐含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;
}

但是您不能做诸如获取其地址之类的事情,否则它会变得奇怪,请参见:定义constexpr静态数据成员

C

在C中,情况与C ++ pre C ++ 17相同,我在以下位置上载了一个示例:“静态”在C中是什么意思?

唯一的区别是在C ++中const隐含static全局变量,但在C中却不隐含:“静态常量”与“常量”的C ++语义

有什么办法可以完全内联吗?

TODO:有什么方法可以完全内联变量,而无需使用任何内存?

就像预处理器一样。

这将需要某种方式:

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

有关:

在Ubuntu 18.10,GCC 8.2.0中进行了测试。


2

是的,它对其他模块隐藏了一个模块中的变量。在C ++中,当我不想/不需要更改.h文件时会使用它,这会触发不必要的其他文件的重建。另外,我把静态放在首位:

static const int foo = 42;

另外,根据其用途,编译器甚至不会为其分配存储空间,而只是“内联”使用它的值。没有静态变量,编译器不能假定它没有在其他地方使用,也不能内联。


2

这是全局常量,仅在编译模块(.cpp文件)中可见/可访问。为此不建议使用static的BTW。更好地使用匿名名称空间和枚举:

namespace
{
  enum
  {
     foo = 42
  };
}

这将迫使编译器不将foo视为常量,从而阻碍了优化。
Nils Pipenbrinck,

枚举值始终是恒定的,因此我看不到它将如何阻碍任何优化
Roskoto

啊-是的。。我的错误。以为您使用了一个简单的int变量。
Nils Pipenbrinck,

罗斯科托,我不清楚enum在这种情况下它有什么好处。关心详细吗?这些enums通常仅用于防止编译器为该值分配任何空间(尽管现代编译器不需要为此使用enumhack)并防止创建指向该值的指针。
康拉德·鲁道夫

Konrad,在这种情况下使用枚举会遇到什么问题?枚举用于需要常量int的情况,这是正确的情况。
罗斯科托

1

将其设为私有仍然意味着它会出现在标题中。我倾向于使用“最弱”的方法。请参阅Scott Meyers撰写的经典文章:http : //www.ddj.com/cpp/184401197(它与功能有关,但也可以在此处应用)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.