C编译器未定义的行为访问其底层对象可能会更改的全局T const * const obj?

时间:2016-02-07 15:40:48

标签: c function pointers compilation const

当使用const指向全局数据区域的指针时,C标准为定义行为和未定义行为指定了哪种C语法,以便与C编译器和某些函数中的程序员进行通信数据区域可能会被修改,而在源的其余部分,全局数据区域应该只读?

如果一个函数func2()通过指针T const * const pObj;访问全局内存区域,那么之前看作只读,但数据区域可能会被另一个函数func()更改,该函数访问通过指针T * const pObj;创建相同的全局内存区域,函数func()func2()直接调用,或者由func2()调用者调用来自全局的任何本地数据副本在任何函数调用之后,是否刷新内存区域,例如临时变量或寄存器中的值?函数调用是一种重置点,它触发存储在寄存器中的所有缓存的非本地数据(无论是否标记为const),还是刷新和刷新本地临时变量?

使用具有多个用于维护状态的内存驻留数据区域的旧C源,我希望大多数源都将这些区域视为只读。但是,某些功能会根据各种事件修改数据区域。

我想要做的是有两种类型的指向这些内存驻留数据区域的指针,这两种指针指向相同的位置,但是它们被定义为使得内存区域被视为只读(T const * const pObj; )或可读/可写(T * const pObj;)。

C标准对这种方法有什么看法以及它的安全性?

例如,在几个文件的以下C源代码示例中,我希望func2()能够正常工作,因为在调用func()之前调用func2()会发生状态更改。 / p>

include文件包含以下定义某些类型的源代码行,以及声明在别处定义的全局变量。

typedef struct { int state; /* other stuff */ } ET;  // Event type
typedef struct { ET state; /* other stuff */ } T;    // memory resident data type

extern T const * const pObjImmutable;  // readonly pointer to readonly memory
extern T * const pObjMutable;  // readonly pointer to read/writable memory
extern int  IsState (const ET state, const ET event);  // check for equivalence of state and event

接下来有一个源文件,其中包含文件中声明的指针实际上与用于操作和更改的API一起定义。

static T objThing;       // define the object but make static for file visibility only
T const * const pObjImmutable = &objThing;  // define global readonly pointer to readonly memory
T * const pObjMutable = &objThing;  // define global readonly pointer to read/writable memory

int  IsState (ET state, ET event)
{
   // check for equivalence of state and event ....
}  

最后,有一个源文件,其中实际使用全局对象。

void func (ET event)
{
    // make changes to objThing based on event
    pObjMutable->state = event;
}

void func2 (void)
{
    extern ET const stdStateOne, const stdStateTwo;

    // use current state of objThing to make decisions
    if (IsState (pObjImmutable->state, stdStateOne)) {
        //  do things for State One due to an event
    } else if (IsState (pObjImmutable->state, stdStateTwo)) {
        //  do things for State Two due to an event
    }
}

int main ()
{
    func (eventOne);    // initialize the state
    func2 ();           // do something based on current state
    func (eventTwo);    // change the state
    func2();            // do something based on current state using new state
}

然而,如果函数func2()调用函数调用函数调用内存驻留区域,那么C标准会说一个函数在调用树的某个地方调用?例如,如果函数func2()直接使用新事件调用函数func(),或者函数func2()调用函数,而函数又调用func()新事件?< / p>

2 个答案:

答案 0 :(得分:1)

此评论不太正确:

extern T const * const pObjImmutable;  // immutable pointer to immutable memory

指针可能指向可变或不可变的内存,我们无法分辨。我们所能说的是 this pointer 不能用来修改那个内存。目标可能是可变的,并且可能在读取此指针之间发生变化;唯一的限制是该指针无法

  

调用一个修改内存驻留区域的函数?

我不确定你到底是什么意思。如果您有以下内容:

static const T constThing;

然后一些代码修改了constThing的存储字节,它会导致未定义的行为。但...

  

例如,如果函数func2()直接用新事件调用函数func(),或者函数func2()调用一个函数,该函数又用新事件调用func()?

如果你只是在谈论非const objThing那么这很好。 pObjMutable用于更新objThing;对于任何以任何方式查看对象的人(包括通过pObjImmutable),他们都会看到更改。

因此,如果你有一些代码:

printf("%d\n", pObjImmutable->bar);
func();
printf("%d\n", pObjImmutable->bar);

编译器无法为第二个printf缓存pObjImmutable->bar的结果,因为func()可能已修改objThing

答案 1 :(得分:0)

由于const限定符表示变量的值不会改变,因此编译器可以自由地执行它想要做的优化,包括将值保存在寄存器或临时值中。如果标记为const的变量或对象实际上通过其他操作进行了更改,则将其视为未定义行为。

所以const T *p = &Thing;意味着指针p中的地址可能会发生变化,尽管指向的对象或变量不会改变(更改变量会导致未定义的行为)。使用T * const p = &Thing;意味着指针p不会改变,尽管指向的对象或变量可能会改变。使用T const * const p = &Thing;意味着不仅指针p中的地址不会改变,而且指向的变量的内容也不会改变。 (见What is the difference between const int*, const int * const, and int const *?

结果是使用extern T const * const p;来访问指针p所指向的全局变量意味着所调用的任何函数都不应该修改其地址所在的对象或变量的内容。指针变量p。这样做会导致未定义的行为。

将volatile限定符与const

一起使用

附加调查表明,将对象作为只读对象呈现的全局指针的适当定义可能会因以下其他活动(如硬件事件或其他线程或进程)而实际发生更改。

T const volatile  * const pObjImmutable = &objThing;  // read only pointer to read only memory which may change somehow

在include文件中声明要访问的其他源文件的此全局变量,我们将使用:

extern T const volatile  * const pObjImmutable;

volatile类型限定符的使用表明指向的对象可能会发生变化(请参阅Why is volatile needed in C?),但const限定符表示不会通过提供的指针修改对象。这来自{6. 3}}

第109页第6.7.3节类型限定词第10段

文件中的相关章节,第5节和第6节,以及两个注释113和114,如下所示。

  

5如果尝试修改用a定义的对象   通过使用具有非const限定的左值的const限定类型   类型,行为是未定义的。如果试图引用一个   通过使用左值使用volatile限定类型定义的对象   如果使用非volatile限定类型,则行为未定义。 113)

     

6具有volatile限定类型的对象可能会以某种方式进行修改   未知的实施或具有其他未知的副作用。   因此,任何涉及这种对象的表达方式都应如此   严格按照抽象机的规则进行评估,如   在5.1.2.3中描述。此外,在每个序列点的值   最后存储在对象中的应与该规定的一致   抽象机,除了由提到的未知因素修改   先前。 114)什么构成对具有的对象的访问   volatile-qualified类型是实现定义的。

     

113)这适用于那些表现得像定义的对象   具有限定类型,即使它们实际上从未被定义为   程序中的对象(例如内存映射的对象)   输入/输出地址)。

     

114)可以使用易失性声明来描述对象   对应于存储器映射的输入/输出端口或对象   由异步中断功能访问。对...的行动   如此声明的对象不应被“优化”   除非规则允许,否则执行或重新排序   评估表达。

stackoverflow WG14/N1124 Committee Draft — May 6, 2005 ISO/IEC 9899:TC2的答案还提到了这篇文章Difference between const & const volatile,其中指出:

  

虽然挥发性(“不断变化”)和const的本质   (“只读”)装饰者可能看起来乍看之下反对,有   有时候使用它们来宣布一个是有意义的   变量。我遇到过的情景都涉及到指针   内存映射硬件寄存器和共享内存区域。

另请参阅文章Combining C’s volatile and const Keywords,其中有关于正确使用volatile限定符的说法。

  

变量应该在其值可能发生变化时声明为volatile   不料。实际上,只有三种类型的变量可以改变:

     
      
  1. 内存映射外设寄存器

  2.   
  3. 由中断服务程序修改的全局变量

  4.   
  5. 多线程应用程序中多个任务访问的全局变量

  6.   

警告多线程应用程序的注意事项

警告的一个注意事项是此构造不提供同步,因此应在多线程应用程序中谨慎使用,多线程引用并修改通过指针引用的全局数据对象。

使用volatile关键字只向编译器发出通知,告知数据对象可能由于某些外部事件而发生更改。多个线程之间没有数据对象的同步。在线程被换出之前,由于部分更新,不能防止数据对象处于不一致状态。在时间片完成时,数据对象可能处于不一致状态,或者正在更新数据对象的线程调用操作系统API,并且在将线程放入等待列表之前更新未完成。

对于使用共享内存由某些硬件更新的复杂数据对象,这似乎也是一个考虑因素。

Andre Alexandrescu在Dobbs博士(2001年C ++ 11之前)中写了一篇关于在多线程应用程序How to Use C's volatile Keyword中使用volatile限定符的文章,volatile: The Multithreaded Programmer's Best Friend描述了{{1}的使用及其对编译器优化以及锁和互斥锁的影响。另请参阅Volatile in C++11,其答案描述了编译器优化,volatile代表C ++ 11。