变量是一个大小为1的数组吗?

时间:2016-09-08 22:30:46

标签: c++ c++11 c++14 language-lawyer c++17

考虑一下:

int main(int, char **) {
  int variable = 21;
  int array[1] = {21};
  using ArrayOf1Int = int[1];
  (*reinterpret_cast<ArrayOf1Int *>(&variable))[0] = 42;
  *reinterpret_cast<int *>(&array) = 42;
  return 0;
}

我是否违反了the strict aliasing rule

或者,正如this comment引导我提出这个问题:变量是一个大小为1的数组

请注意,我将此标记为语言律师问题。因此,我对-fno-strict-aliasing或编译器特定的行为不感兴趣,而是对标准中的内容不感兴趣。另外我认为知道C ++ 03,C ++ 11,C ++ 14和更新版本之间是否以及如何变化会很有趣。

4 个答案:

答案 0 :(得分:9)

显然,如果一个对象是一个大小为1的数组的变量,你可以用对象初始化对大小为1的数组的引用:

int variable{21};
int (&array)[1] = variable; // illegal

但是,初始化是非法的。相关条款是第1款所述的第4条[转换](标准转换):

  

标准转化是具有内置含义的隐式转化。第4条列举了全套此类转换。

这个子句在这里引用的时间太长了但是对于将对象转换为任何大小的数组的引用没有任何意义。类似地,reinterpret_cast(5.2.10 [expr.reinterpret.cast])中的部分没有说明涉及数组的任何行为,但在第1段中明确说明了这一排除:

  

...下面列出了可以使用reinterpret_cast显式执行的转化。使用reinterpret_cast无法明确执行其他转换。

我不认为有一个明确的陈述,即一个对象不是一个对象的数组,但有足够的遗漏来暗示这个案例。标准关联对象和数组给出的保证是指向对象的指针就像它们指向大小为1的数组一样(5.7 [expr.add]第4段):

  

出于这些运算符的目的,指向非阵列对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型。

此语句的存在还意味着数组对象和非数组对象是不同的实体:如果它们被认为是相同的,那么这个语句就没有必要开始。

关于标准的先前(或未来)版本:虽然不同条款中的确切单词可能已经改变,但整体情况并没有改变:对象和数组总是不同的实体,到目前为止,我不知道改变它的意图。

答案 1 :(得分:7)

reinterpret_cast仅在C ++ 11及更高版本中可预测,因此在C ++ 11之前,两行都不能保证定义行为。我们将继续假设C ++ 11或更高版本。

第一行

(*reinterpret_cast<decltype(&array)>(&variable))[0] = 42;

在这一行中,取消引用reinterpret_cast会产生一个glvalue,但不会通过该glvalue 访问 int个对象。在访问int对象时,引用该数组的glvalue已经被衰减为指向该对象的指针(即int*)。

然而,人们可以设法&#34;看起来可能包含严格别名违规的情况,如下所示:

struct S {
    int a[1];
};
int variable = 42;
S s = reinterpret_cast<S&>(variable);

违反严格别名,因为您可以通过聚合或联合类型的子对象访问对象。 (此规则自C ++ 98以来就存在。)

第二行

*reinterpret_cast<decltype(&variable)>(&array) = 42;

reinterpret_cast保证给出指向数组的第一个子对象的指针,该对象是int对象,因此通过int指针对其进行定义是明确定义的。

答案 2 :(得分:6)

最近的一份草案说:

§[expr.unary.op] / 3:

  

一元&amp;的结果operator是指向其操作数的指针。   [...]   出于指针运算(5.7)和比较(5.9,5.10)的目的,不是以这种方式获取地址的数组元素的对象被认为属于具有T类型的一个元素的数组。

我们在这里处理的类型都是真正的指针,但我们(最终)取消引用它们。因此,这可能不足以呈现定义的行为(但它是一个近距离调用)。

关于版本之间的变化:该措辞在N4296(C ++ 14和C ++ 17之间的草案)中,但不是N4140或N3337(分别基本上是C ++ 14和C ++ 11)。

C11标准与fscanf_sfwscanf_s(§K.3.5.3.2/ 4)的语言模糊相似:

  

这些参数中的第一个与fscanf相同。那个论点是   紧跟在参数列表中的第二个参数,它具有类型   rsize_t并给出该对的第一个参数指向的数组中的元素数。如果第一个参数指向标量对象,则认为它是一个元素的数组。

答案 3 :(得分:1)

1个整数的数组与布局不兼容。

这意味着:

struct A {
  int x;
};
struct B {
  int y[1];
};
A a={0};
std::cout << ((B*)&a).y[0];

未定义行为。有关 layout-compatible 的定义,请参阅[basic.types]/11

A::xB::y[basic.types]/10的类型不同 - 一个位于[basic.types]/10.2(标量类型)下,另一个位于[basic.types]/10.4下(数组)文字)。它们不是布局兼容的枚举。它们不是类类型,因此[class.name]/20-21不适用。

因此[class.name]/20(公共初始序列)不会将xy视为常见的初始序列。

我不知道编译器没有使AB实际上逐位相同,但标准规定上述重新解释是不正确的,因此编译器是可以自由地假设它永远不会被完成。这可能导致优化器或其他严格别名的开发者在您依赖它时会导致意外行为。

我个人认为最好说明数组T[N]与N个相邻T s的序列是布局兼容的。这将允许许多有用的技术,例如:

struct pixel {
  union {
    struct {
      char r, g, b, a;
    } e;
    std::array<char,4> pel;
  };
};

其中pixel.pel[0]保证与pixel.e.r对应。但就我所知,这是不合法的。