在R值中使用volatile两次

Geo*_*son 12 c c++ gcc visual-c++

该声明:

volatile unsigned char * volatile p = (volatile unsigned char * volatile)v;
Run Code Online (Sandbox Code Playgroud)

在MSVC v14.1中生成警告C4197:

警告C4197:'volatile unsigned char*volatile':忽略强制转换中的顶级volatile

2011 C标准([N1570] 6.7.3 4.)规定:"与限定类型相关联的属性仅对表达式有意义,即l值",因此此投射中的顶级volatile将被忽略并生成这个警告.

该代码的作者指出,它不违反C标准,并且需要阻止一些GCC优化.他通过以下代码说明了代码的问题:https://godbolt.org/g/xP4eGz

#include <stddef.h>

static void memset_s(void * v, size_t n) {
  volatile unsigned char * p = (volatile unsigned char *)v;
  for(size_t i = 0; i < n; ++i) {
    p[i] = 0;
  }
}

void f1() {
  unsigned char x[4];
  memset_s(x, sizeof x);
}

static void memset_s_volatile_pnt(void * v, size_t n) {
  volatile unsigned char * volatile p = (volatile unsigned char * volatile)v;
  for(size_t i = 0; i < n; ++i) {
    p[i] = 0;
  }
}

void f1_volatile_pnt() {
  unsigned char x[4];
  memset_s_volatile_pnt(x, sizeof x);
}
Run Code Online (Sandbox Code Playgroud)

...他表明函数f1()编译为空(只是一个ret指令),但f1_volatile_pnt()编译成执行预期作业的指令.

问题:是否有正确编写此代码的方法,以便GCC根据2011 C标准(第[N1570] 6.7.3节)正确编译,以便它不会产生MSVC和ICC的警告?......没有#ifdef ......

有关此问题的上下文,请参阅:https://github.com/jedisct1/libsodium/issues/687

Eri*_*hil 10

结论

volatile unsigned char * volatile p = (volatile unsigned char * volatile) v;在没有警告的情况下使用C或C++编译代码并保留作者的意图,请删除强制转换中的第二个volatile:

volatile unsigned char * volatile p = (volatile unsigned char *) v;
Run Code Online (Sandbox Code Playgroud)

在C语言中不需要强制转换,但是问题是在MSVC中编译代码是可编译的而没有警告,MSVC编译为C++,而不是C,因此需要强制转换.仅在C语言中,如果语句可以是(假设vvoid *或与其类型兼容p):

volatile unsigned char * volatile p = v;
Run Code Online (Sandbox Code Playgroud)

为什么要将指针限定为易失性

原始的源包含以下代码:

volatile unsigned char *volatile pnt_ =
    (volatile unsigned char *volatile) pnt;
size_t i = (size_t) 0U;

while (i < len) {
    pnt_[i++] = 0U;
Run Code Online (Sandbox Code Playgroud)

此代码的明显需求是确保为安全目的清除内存.通常,如果C代码为某个对象分配零x并且x在后续分配或程序终止之前从不读取,则编译器在优化时将删除零分配.作者不希望这种优化发生; 他们显然打算确保内存实际被清除.清除内存可以减少攻击者读取内存的机会(通过侧通道,利用错误,通过获取计算机的物理拥有权或其他方式).

假设我们有一些缓冲区x是一个数组unsigned char.如果x定义了volatile,它是一个volatile对象,编译器总是实现对它的写入; 它在优化过程中从不删除它们.

另一方面,如果x没有用volatile定义,但是我们把它的地址放在一个p有类型的指针中,pointer to volatile unsigned char当我们编写时会发生什么*p = 0?正如R ..指出的,如果编译器可以看到这些px,它知道被修改的对象不是易失性的,因此如果编译器可以以其他方式优化掉分配,则不需要编译器实际写入内存.这是因为C标准volatile在访问易失性对象方面定义,而不仅仅是通过具有"指向易失性事物的指针"类型的指针来访问内存.

为确保编译器实际写入x,此代码的作者声明p为volatile.这意味着*p = 0,编译器无法知道p指向的内容x.编译器需要p从它分配的任何内存中加载值p; 它必须假定p可能已从指向的值改变x.

此外,当p声明时volatile unsigned char *volatile p,编译器必须假定指向的位置p是volatile.(从技术上讲,当它加载它的值时p,它可以检查它,发现它实际上指向x或已知其他已知不易变的存储器,然后将其视为非易失性.但这将是一个非凡的努力,编译器,我们可以假设它不会发生.)

因此,如果代码是:

volatile unsigned char *pnt_ = pnt;
size_t i = (size_t) 0U;

while (i < len) {
    pnt_[i++] = 0U;
Run Code Online (Sandbox Code Playgroud)

然后,每当编译器看到pnt实际上指向非易失性存储器并且在稍后写入之前未读取该存储器时,编译器可以在优化期间移除该代码.但是,如果代码是:

volatile unsigned char *volatile pnt_ = pnt;
size_t i = (size_t) 0U;

while (i < len) {
    pnt_[i++] = 0U;
Run Code Online (Sandbox Code Playgroud)

然后,在循环的每次迭代中,编译器必须:

  • pnt_从为其分配的内存中加载.
  • 计算目的地地址.
  • 将零写入该地址(除非编译器遇到确定地址非常不稳定的特殊麻烦).

因此,第二个目的volatile是从编译器中隐藏指针指向非易失性存储器的事实.

虽然这实现了作者的目标,但是它具有强制编译器在循环的每次迭代中重新加载指针并且阻止编译器通过一次写入目标几个字节来优化循环的不期望的效果.

铸造价值

考虑定义:

volatile unsigned char * volatile p = (volatile unsigned char * volatile) v;
Run Code Online (Sandbox Code Playgroud)

我们已经在上面看到,pas 的定义volatile unsigned char * volatile是完成作者目标所必需的,虽然它是C中缺点的一个不幸的解决方法.但是,演员怎么样,(volatile unsigned char * volatile).

首先,v转换是不必要的,因为值将自动转换为类型p.为了避免MSVC中的警告,可以简单地删除转换,将定义保留为volatile unsigned char * volatile p = v;.

鉴于演员阵容在那里,问题是问第二个是否volatile有任何意义.C标准明确指出"与限定类型相关联的属性仅对于左值的表达式有意义."(C 2011 [N1570] 6.7.3 4.)

volatile意味着编译器未知的东西可以改变对象的值.例如,如果程序中有a volatile int a,则表示a可以通过编译器不知道的某种方式更改标识的对象.它可以通过计算机上的某些特殊硬件,调试器,操作系统或其他方式进行更改.

volatile修改对象.对象是存储器中可以表示值的数据存储区域.

在表达式中,我们有价值观.例如,某些int值为3,5或-1.值不能波动.它们不是存储在内存中; 它们是抽象的数学价值.3号永远不会改变; 它总是3.

演员(volatile unsigned char * volatile)表示要将某些内容转换为易失性unsigned char的易失性指针.可以指向volatile unsigned char-a指针指向内存中的某些内容.但是,作为一个易变的指针是什么意思?指针只是一个值; 这是一个地址.值没有内存,它们不是对象,因此它们不能是volatile.因此,volatile演员中的第二个在(volatile unsigned char * volatile)标准C中没有效果.它符合C代码,但限定符没有效果.

  • @KarolaN:不需要演员阵容.演员是合法的(符合)C.演员阵容中的第二个"挥发性"没有效果.为了作者的目的,需要在指针定义中使用第二个`volatile`.我希望MSVC警告关于强制转换中第二个`volatile`无效的事实不会阻止MSVC正确清除内存.虽然转换中的第二个`volatile`没有效果,但指针定义中的第二个`volatile`确实有效,而MSVC应该尊重它.(但是,请注意你问过C,我相信MSVC只做C++,而不是真正的C.) (2认同)