C/C++ 更罕见的关键字 - 寄存器、易失性、外部、显式
C/C++ Rarer keywords - register, volatile, extern, explicit
你能给我一个快速的介绍一下这4个关键词的用途和原因吗?
我了解谷歌会告诉你的关于注册和易失性的基础知识,但想了解更多(只是一个实用的概述(。 外部和显式让我有点困惑,因为我从未找到必须自己使用它们的理由,尽管做了相当低级的嵌入式系统代码。 同样,我可以谷歌,但我更喜欢专家的快速,实用的摘要,这样它就会留在我的脑海中。
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文件中实例化此模板。这样,您可以控制模板的实例化位置。明智地使用它可以大大缩短编译时间。
明确
这用于防止类型的自动转换。如果您有具有以下构造函数的类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
对你来说很少见,那么你对它的使用还不够。您应该始终使用它,除非您特别想要转换。这并不常见。
挥发性的
这会阻止某些有用的优化。通常,如果你有一个变量,C/C++会假设它的内容只有在显式更改它们时才会改变。因此,如果将int someInteger;
声明为全局变量,C/C++ 编译器可以在本地缓存该值,而不是每次使用它时都不断访问该值。
有时,您想阻止这种情况。在这些情况下,您使用 volatile
;这会阻止这些优化。
注册
这只是一个提示。它告诉编译器尝试将变量的数据放入寄存器中。这基本上是不必要的;编译器比你更擅长决定什么应该和不应该是寄存器。
register
用作编译器的提示,即变量应存储在寄存器中而不是堆栈中。编译器经常会忽略这一点,为所欲为;如果可能的话,变量将被分配到寄存器中。
volatile
表示内存可能会更改,而程序实际上没有执行任何操作。这是编译器的另一个提示,即它应该避免优化对该位置的访问。例如,如果对同一位置有两次连续写入,而没有干预读取,则编译器可能会优化第一个。但是,如果您要写入的位置是硬件寄存器,则需要每次写入都完全按照写入的方式进行。所以volatile
就像在说"相信我就行了"。
extern
表示定义发生在当前文件之外。对于全局变量很有用,但通常隐含在声明中。正如 Blindy 所指出的,它对于指示函数应该具有 C 链接也很有用。具有 C 链接的函数将使用其实际名称作为输出可执行文件中的符号进行编译。C++函数包含更多信息,例如其符号中的参数类型。这就是为什么重载在 C++ 中有效,但在 C 中不起作用。
explicit
适用于C++构造函数。这意味着不应隐式调用构造函数。例如,假设您有一个 Array
类,其构造函数接受整数capacity
。您不希望仅仅因为存在整数构造函数而将整数值隐式转换为Array
对象。
> register
这些天大多被忽略了,但它应该向编译器暗示你宁愿变量在寄存器中而不是在堆栈上。如今,优化器做得很好,现在无关紧要。
volatile
告诉编译器不要假定该值仅从当前线程更改,因此它将始终读取实际的内存值,而不是在读取之间缓存它。它对于多线程应用程序很有用,但无论如何最好使用操作系统原语(事件、信号量等(。
extern
不定义值,它只声明它。它主要用于从DLL或共享库(.a
(导出和导入函数。副作用还允许您使用 extern "C"
关闭名称重整以进行C++。
explicit
允许您指定构造函数必须是显式的,而不是隐式转换构造函数(如果它具有采用int
的隐式构造函数,则可以编写CMyClass val=10;
(。
寄存器用于指示编译器使用寄存器来存储此值,现代编译器优化以在for循环等期间使用它。
易失性是可由外部进程或在多线程应用程序运行时期间更改的变量。
Extern 告诉链接器变量在不同的文件中定义。
显式指示编译器不允许类型的隐式转换。
> 1:当您希望强制将值保留在寄存器中而不是保存在RAM中时,使用寄存器。例如,您可以执行以下操作:
register int x;
这会让编译器知道您希望将此 int 放置在 CPU 寄存器中,这应该可以让您更快地访问它。但是,您的编译器可以在优化过程中忽略此关键字,并且大多数情况下,如果需要,好的编译器会将变量放在寄存器中。在我看来,现在这主要是一个多余的关键字。
2:易失性向编译器暗示变量会定期更改,例如,如果您将某些浮点数定义为易失性:
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:显式适用于不希望在构造函数中进行隐式转换的情况,有关详细信息,请参阅此线程。它主要用于调试目的,并确保在执行 OOP 时遵守更严格的规则。
- 易失性sig_atomic_t的内存安全性
- C++易失性:保证 32 位访问?
- 避免易失性和非易失性成员函数的代码重复
- 当 2 个线程共享同一物理内核时,具有错误共享的易失性增量在发布中的运行速度比在调试中慢
- 如何访问常量易失性 std::array?
- 为什么在 C++20 中弃用易失性?
- 根据 MSVC,具有易失性成员的结构不再是 POD
- 是否允许编译器优化掉局部易失性变量
- 访问共享内存而不使用易失性、std::atomic、信号量、互斥锁和自旋锁
- 如何避免对无锁程序使用易失性?
- C++:易失性实例中的易失性成员函数 - 将数组分配给指针是无效的转换?
- g++ 6.3,avx 内联函数上的 Kahan 求和用易失性关键字进行序列化
- 是什么让这种易失性打破了结构的指针算法?
- 如果不需要易失性,为什么 std::atomic 方法会提供易失性重载
- *(易失性无符号整数 *) 的含义 0x00 = 0x00;
- 使用易失性 c 字符串和 std::cout
- "static_cast<易失性空隙>"对优化器意味着什么?
- MS中的内联组装是否需要易失性之类的东西C++以防止优化器干预
- 如何使用易失性多映射迭代器
- C/C++ 更罕见的关键字 - 寄存器、易失性、外部、显式