C/C++中较少使用的关键字 - register、volatile、extern、explicit

16
你能给我快速介绍一下这四个关键词的用途和原因吗?
我了解注册(register)和易失性(volatile)的基础知识,但想要更多了解(只是一个实际的概述)。Extern和explicit让我有些困惑,因为尽管我做过相对低级别的嵌入式系统代码,但从未找到使用它们的理由。再次强调,我可以使用谷歌,但我更愿意从专家那里得到快速、实用的摘要,这样就更容易记住了。

6
你有尝试过使用标准的C++教材,甚至是维基百科吗? - Kerrek SB
8
"extern"很少用? - BoltClock
2
“explicit(显式)”关键字肯定不是“更少见”的关键字。我使用“explicit”关键字的次数比其他三个关键字加在一起还要多。 - In silico
2
有趣的是,我从来没有在我的生活中使用过 explicitregister,但经常使用 extern,有时甚至使用 volatile - Blindy
3
你漏掉了“restrict”这个词。 - Chris Lutz
显示剩余3条评论
5个回答

29

extern

extern有多种用法。对于全局变量来说,它表示声明变量而不是定义。这对于将全局变量放在头文件中非常有用。如果你在头文件中放置了以下代码:

int someInteger;

每个包含该头文件的.cpp文件都会尝试拥有自己的someInteger。这将导致链接错误。通过使用extern声明,你所表达的意思是在代码中会有一个someInteger变量:

extern int someInteger;

现在,在 .cpp 文件中,您可以定义 int someInteger ,这样就会有一个确切的副本。

还有 extern "C",它用于指定某些函数使用 C 连接规则而不是 C++。这对于与作为 C 编译的库和代码进行交互很有用。

在 C++0x 中,还将有 extern template 声明。它们是显式模板实例化的反面。当您执行此操作时:

template class std::vector<int>;

你在告诉编译器立即实例化这个模板。通常情况下,实例化要等到第一次使用模板时才会延迟进行。在C++0x中,你可以这样说:

extern template class std::vector<int>;

这告诉编译器在这个.cpp文件中不要实例化这个模板,永远不要。这样,您可以控制模板的实例化位置。明智地使用它可以大大提高编译时间。

explicit

这用于防止类型自动转换。如果您有一个带有以下构造函数的类ClassName

ClassName(int someInteger);

这意味着如果你有一个接受 ClassName 的函数,用户可以传入一个 int,转换将会自动完成。

void SomeFunc(const ClassName &className);
SomeFunc(3);

那是合法的,因为ClassName有一个接受整数的转换构造函数。这也是为什么可以将std::string传递给一个带有char*参数的函数;std::string有一个接受char*的构造函数。

然而,大多数情况下,您不希望进行隐式转换。通常,您只想进行显式转换。是的,像std::string这样有时是有用的,但是对于不恰当的转换,您需要一种关闭它的方式。这就引入了explicit

explicit ClassName(int someInteger);

这将防止隐式转换。你仍然可以使用 SomeFunc(ClassName(3));,但是 SomeFunc(3) 将不再起作用。

顺便说一句:如果对你来说 explicit 很少使用,那么你没有充分利用它。除非特别需要转换,否则应该始终使用它,而这种情况并不经常发生。

volatile

这会阻止某些有用的优化。通常,如果你有一个变量,C/C++ 会假定它的内容只在显式更改时才会改变。因此,如果你将 int someInteger; 声明为全局变量,C/C++ 编译器可以在本地缓存值,并且不会在每次使用它时都不断访问该值。

有时,你希望停止这种行为。在这些情况下,你可以使用 volatile,这会阻止这些优化。

register

这只是一个提示。它告诉编译器尝试将变量数据放入寄存器中。实际上,这是不必要的,编译器比你更擅长决定应该在寄存器中存储哪些变量。


4
总体而言,回答很棒。关于volatile,它只在周围的代码/流程中显式更改时才会更改,而不是通过中断处理程序或其他线程。此外,volatile还确保对变量的写入创建机器级别的内存写入(而不仅仅是寄存器)-这并不一定意味着CPU的特定核心缓存会将该写入刷新到物理内存,因此它不是内存屏障或锁的替代品。 - Tony Delroy
register 的一个可能合理的用例是:一些 CPU 拥有比相关内存内容更大的寄存器 - 例如,80 位浮点寄存器,在写入内存时舍入为 64 位双精度。也许明确使用 register 可以帮助控制何时执行精度损失,优先考虑特定值(如总数),尽管在更新该总数之间使用了许多其他值进行计算...? - Tony Delroy
1
“register” 在嵌入式编程中非常有用,特别是在需要测量两个事件之间 CPU 循环的精确时间时。对于嵌入式编译器来说,“register” 关键字通常不仅仅是一个提示,而是一个严格的命令。 - vsz

7

register 用作向编译器的提示,表明一个变量应该被存储在寄存器中而不是堆栈中。编译器经常会忽略这一点并按照自己的意愿来进行分配,无论如何,如果可能的话变量会被分配到寄存器中。

volatile 表示内存可能在程序实际上并没有做任何操作时发生改变。这是另一个提示,告诉编译器应该避免优化对该位置的访问。例如,如果您连续写入两次到同一位置而没有读取,编译器可能会优化掉第一次写入。然而,如果你要写入的位置是一个硬件寄存器,你需要每次写入都精确地执行,所以 volatile 就像是在说“相信我吧”。

extern 表示定义发生在当前文件之外。对于全局变量很有用,但通常在声明中已经隐含了这个意思。正如 Blindy 所指出的那样,它也适用于指示函数应该具有 C 连接。具有 C 连接的函数将使用其实际名称作为输出可执行文件中的符号进行编译。C++ 函数包括更多的信息,比如它们的参数类型在它们的符号中。这就是为什么 C++ 中的重载可以工作而 C 中不能。

explicit 适用于 C++ 构造函数。它意味着构造函数不应该被隐式调用。例如,假设你有一个 Array 类,它有一个接受整数 capacity 的构造函数。你不希望整数值被隐式转换成 Array 对象,只因为有一个整数构造函数。


2

register这个关键字在现代编程中被忽略了,它的作用是告诉编译器你更希望变量存储在寄存器中而不是栈中。但现代优化器已经做得非常好了,所以现在已经没有意义了。

volatile关键字告诉编译器不要假设该值只会从当前线程改变,因此它将始终读取实际的内存值而不是在读取之间缓存它。这对于多线程应用程序很有用,但您最好仍然使用操作系统原语(例如事件、信号量等)。

extern关键字并不定义变量的值,它只是声明变量。它主要用于从DLL或共享库(.a)中导出和导入函数。一个副作用也允许您使用extern "C"关闭C++名称重整。

explicit关键字允许您指定构造函数必须是显式的,而不是隐式转换构造函数(如果它具有接受int的隐式构造函数,则可以编写CMyClass val=10;)。


1
“volatile” 是必需的,以便与硬件共享访问,没有操作系统原语可以帮助你。 - littleadv
我在谈论用户空间程序,操作系统通常提供API以允许对硬件寄存器进行“良好”的访问。当然,内核代码是另一回事。 - Blindy

1

register 指令用于引导编译器使用寄存器来存储该值,在现代编译器中,优化器会在for循环等情况下使用它。

volatile 变量可以被外部进程修改或在多线程应用程序运行时期间改变。

extern 声明告诉连接器变量的定义在另一个文件中。

explicit 指令告诉编译器不允许类型的隐式转换。


1

1:当您希望强制将值保留在寄存器而不是RAM中时,可以使用Register。例如,您可能会执行以下操作:

register int x;

这将让编译器知道您希望将此int放置在CPU寄存器中,这应该可以更快地访问它。但是,在优化过程中,您的编译器可以忽略此关键字,大多数情况下,如果需要,好的编译器将自动将变量放置在寄存器中。在我看来,这主要是一个多余的关键字。

2:Volatile提示编译器变量会经常更改,例如,如果您将某些浮点定义为volatile:

volatile float flt;

这告诉编译器您希望对经常更改的变量执行特定的优化。它还说明该变量可能会在活动程序没有输入的情况下发生更改。再次强调,现在主要是多线程编程中使用的冗余关键字。

3:Extern告诉编译器定义在另一个文件中而不是声明变量的文件中,例如,您可能有一些通用头文件,您在大多数文件中都包含它,在这里您可能想声明一些全局指针,因此您将执行以下操作:

extern MyClass* g_pClassPointer;

然后您将在 cpp 文件的顶部声明您的 MyClass 已经被实现:

MyClass* g_pClassPointer = nullptr;

extern关键字还用于向编译器声明您正在使用原始的C代码或ASM代码,例如,您可以执行以下操作:

extern __asm {
    mov eax, 2
    mov ebx, 3
    add eax, ebx
}

或者如果你只想使用原始的C代码,你可以使用extern "C",你的编译器会识别它。

4:当你不想在构造函数中进行隐式转换时,可以使用Explicit。更多信息请参见this thread。它主要用于调试目的,并确保在进行面向对象编程时遵守更严格的规则。


2
-1:volatile 不是“提示”。它表示“这些访问必须按照我要求的相同顺序和相同次数发生”。在低级嵌入式系统中,它经常用于与硬件通信(以及多线程代码中 - 但通常有比通过 volatile 更好的通信方式)。 - Martin Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接