`*(volatile T*)0x1234;` 是否保证转换为读取指令?
Is `*(volatile T*)0x1234;` guaranteed to translate into read instruction?
在使用硬件时,有时需要从特定寄存器执行读取并丢弃实际值(例如,清除一些标志)。一种方法是显式读取并丢弃值,例如:
int temp = *(volatile int*)0x1234; // 0x1234 is the register address
(void)temp; // To silence the "unused" warning
另一种似乎有效的方法很简单:
*(volatile int*)0x1234;
但这似乎并不明显暗示 read 访问权限,但它似乎转换为我检查过的编译器。这个有标准保证吗?
带有 -O3
的 ARM GCC 示例:
https://arm.godbolt.org/z/9Vmt6n
void test(void)
{
*(volatile int *)0x1234;
}
转换为
test():
mov r3, #4096
ldr r3, [r3, #564]
bx lr
C 2018 6.7.3 8 说:
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3.…
由于 *(volatile int*)0x1234;
是引用具有 volatile 限定类型的对象的表达式,因此对其求值必须访问该对象。 (当然,这假定 0x1234
代表对 C 实现中某个对象的有效引用。)
根据 C 2018 5.1.2.3 4:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
根据 C 2018 6.5 1:
An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.
因此,表达式指定了值的计算。 5.1.2.3 4 段告诉我们这个评估是由抽象机执行的,6.7.3 8 告诉我们实际实现执行抽象机执行的这个评估。
需要注意的是,“访问”的构成是实现定义的。 C标准定义的“访问”包括读取和写入(C 3.1 1),但C标准无法指定它是指读取或写入某个特定的硬件。
为了进一步深入到语言律师、领域,C 6.3.2.1 2 告诉我们:
Except when it is the operand of the sizeof
operator, the unary &
operator, the ++
operator, the --
operator, or the left operand of the .
operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion.
因此,由于 *(volatile int*)0x1234;
是一个左值,借助 *
运算符,并且不是列出的运算符的操作数,它被转换为存储在对象中的值。因此,此表达式指定了存储在对象中的值的计算。
volatile 上的 gcc 文档告诉我们,易失性访问的构成是实现定义的:
C has the concept of volatile objects. These are normally accessed by pointers and used for accessing hardware or inter-thread communication. The standard encourages compilers to refrain from optimizations concerning accesses to volatile objects, but leaves it implementation defined as to what constitutes a volatile access. The minimum requirement is that at a sequence point all previous accesses to volatile objects have stabilized and no subsequent accesses have occurred. Thus an implementation is free to reorder and combine volatile accesses that occur between sequence points, but cannot do so for accesses across a sequence point. The use of volatile does not allow you to violate the restriction on updating objects multiple times between two sequence points.
这由 C11 部分支持 6.7.3 类型限定符
p7:
An object that has volatile-qualified type may be modified in ways unknown to the
implementation or have other unknown side effects. Therefore any expression referring
to such an object shall be evaluated strictly according to the rules of the abstract machine,
as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the
object shall agree with that prescribed by the abstract machine, except as modified by the
unknown factors mentioned previously.134) What constitutes an access to an object that
has volatile-qualified type is implementation-defined.
gcc 文档继续指定 volatile 如何为 gcc 工作,对于类似于您所说的情况:
A scalar volatile object is read when it is accessed in a void
context:
volatile int *src = somevalue;
*src;
Such expressions are rvalues, and GCC implements this as a read of the
volatile object being pointed to.
在使用硬件时,有时需要从特定寄存器执行读取并丢弃实际值(例如,清除一些标志)。一种方法是显式读取并丢弃值,例如:
int temp = *(volatile int*)0x1234; // 0x1234 is the register address
(void)temp; // To silence the "unused" warning
另一种似乎有效的方法很简单:
*(volatile int*)0x1234;
但这似乎并不明显暗示 read 访问权限,但它似乎转换为我检查过的编译器。这个有标准保证吗?
带有 -O3
的 ARM GCC 示例:
https://arm.godbolt.org/z/9Vmt6n
void test(void)
{
*(volatile int *)0x1234;
}
转换为
test():
mov r3, #4096
ldr r3, [r3, #564]
bx lr
C 2018 6.7.3 8 说:
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3.…
由于 *(volatile int*)0x1234;
是引用具有 volatile 限定类型的对象的表达式,因此对其求值必须访问该对象。 (当然,这假定 0x1234
代表对 C 实现中某个对象的有效引用。)
根据 C 2018 5.1.2.3 4:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
根据 C 2018 6.5 1:
An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.
因此,表达式指定了值的计算。 5.1.2.3 4 段告诉我们这个评估是由抽象机执行的,6.7.3 8 告诉我们实际实现执行抽象机执行的这个评估。
需要注意的是,“访问”的构成是实现定义的。 C标准定义的“访问”包括读取和写入(C 3.1 1),但C标准无法指定它是指读取或写入某个特定的硬件。
为了进一步深入到语言律师、领域,C 6.3.2.1 2 告诉我们:
Except when it is the operand of the
sizeof
operator, the unary&
operator, the++
operator, the--
operator, or the left operand of the.
operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion.
因此,由于 *(volatile int*)0x1234;
是一个左值,借助 *
运算符,并且不是列出的运算符的操作数,它被转换为存储在对象中的值。因此,此表达式指定了存储在对象中的值的计算。
volatile 上的 gcc 文档告诉我们,易失性访问的构成是实现定义的:
C has the concept of volatile objects. These are normally accessed by pointers and used for accessing hardware or inter-thread communication. The standard encourages compilers to refrain from optimizations concerning accesses to volatile objects, but leaves it implementation defined as to what constitutes a volatile access. The minimum requirement is that at a sequence point all previous accesses to volatile objects have stabilized and no subsequent accesses have occurred. Thus an implementation is free to reorder and combine volatile accesses that occur between sequence points, but cannot do so for accesses across a sequence point. The use of volatile does not allow you to violate the restriction on updating objects multiple times between two sequence points.
这由 C11 部分支持 6.7.3 类型限定符 p7:
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.
gcc 文档继续指定 volatile 如何为 gcc 工作,对于类似于您所说的情况:
A scalar volatile object is read when it is accessed in a void context:
volatile int *src = somevalue; *src;
Such expressions are rvalues, and GCC implements this as a read of the volatile object being pointed to.