静态常量变量是线程安全的吗?

10

我想知道静态常量变量是否是线程安全的?

示例代码片段:

void foo(int n)
{
     static const char *a[] = {"foo","bar","egg","spam"};
     if( ... ) {
       ...
      }
}
5个回答

17
任何从未被修改的变量,无论是否显式声明为const,在本质上都是线程安全的。
const并非编译器保证变量不可变的标志。const仅仅是你向编译器做出的承诺,即该变量永远不会被修改。如果你食言了这个承诺,编译器会报错提醒你,但你总是可以通过去除const属性来使编译器静默。

你永远不应该抛弃你的良心。 - ycomp

14
为了确保真正安全,您应该这样做:

static char const*const a[]

这会禁止修改数据表中的所有指针被修改。
顺便说一句,我更喜欢在类型名后写const,这样一眼就能看出const适用于哪里,即它的左边。

好的评论。您似乎在暗示如果他同时编写const,它将是线程安全的,我理解得对吗? - Jean-Simon Brochu

7
在你的例子中,指针本身可以被视为线程安全的。它将被初始化一次,并且不会在以后被修改。
然而,所指向的内存内容完全不是线程安全的。

假设没有编程错误导致缓冲区溢出或下溢,那么内存不具备线程安全性的前提是什么? - Lazarus
1
并发访问共享资源(在本例中为内存)可能会导致一些损坏或不良行为。为了避免这种问题,对该资源的访问必须以某种方式进行序列化。通常可以使用互斥量或任何更高级别的对象(如关键部分)来实现此目的。有关更多信息,请参阅以下几个有趣的信息:http://www.c2.com/cgi/wiki?ThreadSafe http://en.wikipedia.org/wiki/Thread_safety - Grimmy
2
指针可以被修改,但实际字符串中的字符不能被修改。 - Matthew Flaschen
2
这只是写作。多个线程可以同时读取任何给定的内存块,没有问题。在这种情况下,如果所有数据都是const(指针和内容),那么就没有线程安全问题——如果他确保它只被初始化一次。 - Puppy
4
-1 const foo* 表示指针指向的 foo 是不可更改的,而 并非 指针本身不可更改。 - JeremyP

3
在这个例子中,a 不是 const。它是一个指向 const 字符串的指针数组。如果你想要让 a 本身成为 const,你需要:
static const char *const a[] = {"foo","bar","egg","spam"};

无论是否使用const,只要您不从任何线程中写入数据,从多个线程读取数据始终是安全的。

另外需要注意的是,在声明指向常量字符串的指针数组时,通常不是一个好的做法,特别是在可能用于共享库的代码中,因为这会导致大量重定位,并且数据无法位于实际的常量部分。更好的技术是:

static const char a[][5] = {"foo","bar","egg","spam"};

5是被选择的数字,以适应所有字符串。如果字符串长度可变且您不需要快速访问它们(例如,如果它们是像strerror这样的函数返回的错误消息),那么像这样存储它们是最有效的:

static const char a[] = "foo\0bar\0egg\0spam\0";

您可以使用以下方式访问第n个字符串:

const char *s;
for (i=0, s=a; i<n && *s; s+=strlen(s)+1);
return s;

注意最后的\0很重要。它会使得字符串末尾有两个0字节,如果n越界,就能停止循环。或者你可以提前检查n是否越界。

为什么编译器不能将这些数据放入常量段?数组项的位对位值直到加载时才能确定,但加载程序应该没有问题填充这些内容。问题出在期望常量段即使在代码执行期间也是可重定位的系统上吗? - supercat
如果在加载时被加载器修改,我不会称其为常量区段。这会为每个使用库的进程带来物理内存负担和显著的加载时间开销。实际的常量区段是指磁盘上实际库文件的共享内存映射图像。如果您认为我应该在答案中澄清这一点,我可以这样做。 - R.. GitHub STOP HELPING ICE

3

静态常量字符指针数组的定义方式如下:static const char *a[] = {"foo","bar","egg","spam"};

在C语言中,这种定义方式始终是线程安全的。因为结构体已经在编译时创建好了,所以在运行时不需要额外的操作,因此不存在竞争条件。

但要注意C++的兼容性。静态常量对象会在第一次进入函数时初始化,但该初始化在语言上不能保证是线程安全的。也就是说,当两个不同的线程同时进入该函数并尝试并行初始化对象时,就会存在竞争条件。

但即使在C++中,POD(普通的旧数据:不使用C++特性的结构体,例如你的示例)也会像C语言那样表现。


任何时候,其中一个线程都可以执行例如 a[0] = "faa" 的操作,因此,如果您提供一些额外的保证,那么这只有在给出某些额外保证的情况下才是线程安全的,但本质上这不是线程安全的。 - Jens Gustedt
1
@Jens Gustedt: 是的,但我对问题的理解是初始化是否线程安全。初始化是线程安全的。使用时,请遵循通常的规则。 - Dummy00001

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