C中的易变和变量修改

时间:2018-03-21 23:47:48

标签: c volatile

结构TsMyStruct作为参数提供给某些函数:

typedef struct
{
    uint16_t inc1;
    uint16_t inc2;
}TsMyStruct;


void func1(TsMyStruct* myStruct)
{
    myStruct->inc1 += 1;
}

void func2(TsMyStruct* myStruct)
{
    myStruct->inc1 += 2;
    myStruct->inc2 += 3;
}

在非中断上下文中调用func1,在中断上下文下调用func2。 func2的调用堆栈有一个中断向量作为原点。 C编译器不知道可以调用func2(但代码不被视为"未使用"代码作为链接器在中断向量表内存部分需要它),因此一些代码读取myStruct-> inc2外部可以优化func2,防止myStruct-> inc2从ram重新加载。 C基本类型确实如此,但对于inc2结构成员或某些数组是否正确?这是函数参数吗?

作为一般规则,我可以说"在中断上下文中修改并在其他地方读取的每个内存区域(基本类型?或不?)必须声明为volatile"?

2 个答案:

答案 0 :(得分:2)

是的,在中断处理程序内部和外部使用的任何内存都应该是volatile,包括结构和数组,以及作为函数参数传递的指针。假设您的目标是单核设备,则无需额外同步。

但是,您必须考虑func1可能在任何地方中断,如果您不小心,可能会导致结果不一致。例如,考虑一下:

void func1(volatile TsMyStruct* myStruct)
{
    myStruct->inc1 += 1;
    if (myStruct->inc1 == 4)
    {
        print(myStruct->inc1); // assume "print" exists
    }
}

void func2(volatile TsMyStruct* myStruct)
{
    myStruct->inc1 += 2;
    myStruct->inc2 += 3;
}

由于中断是异步的,因此可以打印不同于4的数字。例如,如果func1在检查后但在print调用之前被中断,则会发生这种情况。

答案 1 :(得分:0)

没有。 volatile还不够。您必须为编译器(可以volatile)和处理器设置优化障碍。例如。当CPU核心写入数据时,这可以进入某个缓存,而对于另一个核心则不可见。

通常,您需要在代码中锁定一些(自旋锁或互斥锁)。此类函数通常包含优化屏障,因此您不需要volatile

你的代码很活泼,正确的锁定看起来像是

void func1(TsMyStruct* myStruct)
{
    lock();
    myStruct->inc1 += 1;
    unlock();
}

void func2(TsMyStruct* myStruct)
{
    lock();
    myStruct->inc1 += 2;
    unlock();
    myStruct->inc1 += 3;
}

并且lock() + unlock()函数包含优化障碍(例如__asm__ __volatile__("" ::: "memory")或仅调用全局函数),这将导致编译器重新加载myStruct。< / p>

对于挑剔:lock()unlock()应该做正确的事情(例如禁用irqs)。现实世界的实施将是例如linux中的spin_lock_irqsave() + spin_lock_irqrestore()