编译器相关 - 这两个C代码真的相同吗?

时间:2012-04-25 09:54:33

标签: c multithreading compiler-construction rtos

在多线程或RTOS环境中,这些代码是否完全相同?

我相信他们不是。但是第一个代码绝对保存在多线程环境中吗?编译器是否有规则为'ga'分配一个寄存器,以后在func_a()中不会再读'ga'?

我知道我可以使用锁,但这不是关于如何保护数据的问题。这只是关于编译器行为的问题。

// ga是一个全局变量。

int func_a() {

    int a = ga;
    return a>2 ? a-2 : 2-a;
}

int func_b() {

    return ga>2 ? ga-2 : 2-ga;
}

我的目的是寻找一种标准方式(不是平台特定的)来只读取一次ga并将其值赋给局部变量'a'。

无论“ga”是否发生变化,都可以始终如一地使用“a”。

4 个答案:

答案 0 :(得分:2)

这些版本的代码在执行函数的多个线程面前都有未定义的行为。当然,不同的编译器可以在将全局变量保存到寄存器中时做不同的事情。更重要的是,无法保证分配到局部变量可以相对于改变全局变量的线程以原子方式完成。

答案 1 :(得分:2)

C标准中没有规则要求编译器以不同方式实现这些功能。例如使用寄存器时,编译器可能会也可能不会“优化”从gaa的分配(即通过'优化',我的意思是:将ga加载到REG中,然后使用相同的REG来完成剩余的计算,将其用作a)。或者它可能不会这样做。

如果要实现无锁数据结构:

  1. C99没有任何可以帮助你的东西。
  2. C11(最近的标准)为您提供原子数据类型。
  3. 如果您使用的是C99,则需要:

    1. 使用锁(因此,不是无锁代码)
    2. 准备编写特定于体系结构的代码。您需要做的最少的是使用一组最小的原子操作,如this library中所做的那样,它使用x86,x86_64和ARM ISA提供的原子操作实现无锁数据结构。
    3. 在这个答案的早期版本中,我提到了一个方面问题(与volatile有关,而且与你的真实无关问题):

      有一个案例可以限制func_b的实施方式,但我实际上是在切线:如果ga被声明为volatile。< / p>

      如果ga是易变的,那么ga 上的每次读取都必须重新从内存中加载ga。即在func_b中,ga将从内存中加载两次。一旦进行比较,一次计算返回值。例如,预期的用途是ga指的是内存映射的I / O端口。然后,如果ga的值在两次读取之间发生变化,则这将反映在返回值中。但是,如果您在另一个线程中更改ga,请不要指望理智/定义的行为。

      另一方面,拥有volatile限定符并不意味着ga只能在func_b中读取一次{{1}}。并且没有限定词是“与易变性相反的”。

答案 2 :(得分:0)

行为取决于您使用的编译器,每个编译器都有自己的优化规则。

答案 3 :(得分:0)

这两个片段可能会以相同的机器代码结束。在多线程情况下,它们都不安全。

volatile会强制创建一个临时变量,但由于从“ga”到volatile变量的副本不能保证是原子的,所以这不是线程安全的。

编写此类代码的唯一安全方法是使用警卫:

int func_a() {

    mtx_lock(&ga_mutex);
    int a = ga;
    mtx_unlock(&ga_mutex);

    return a>2 ? a-2 : 2-a;
}