当结构体将零长度数组作为唯一成员时,数组下标越界

问题描述

在以下代码中:

#include <cstring>

template <unsigned len>
struct Chararray {
  Chararray() {
    memset(data_,len);
  }
  char data_[len];
};

struct Foobar {
  Chararray<5> a;
  Chararray<3> b;
  Chararray<0> c;
};

int main() {
  Foobar f;
}

类型 Chararray<0> 最终将一个大小为零的数组作为其唯一成员。我知道这是一个 GCC 扩展和一般不安全的做法。问题不在于这个。

当我用 gcc 10.2.0 编译代码时,我收到以下警告:

<source>: In function 'int main()':
<source>:5:3: warning: array subscript 8 is outside array bounds of 'Foobar [1]' [-Warray-bounds]
    5 |   Chararray() {
      |   ^~~~~~~~~
<source>:18:10: note: while referencing 'f'
   18 |   Foobar f;
      |          ^

使用 gcc9 及更早版本时没有警告。

问题:下标8从哪里来?那里提到的 Foobar [1] 是什么?看起来有一个 Foobars 的数组,我们正在尝试访问该数组中的元素 8。不知道怎么会这样。如果有人知道细节,如果你能解释一下,我将不胜感激。

在使用 gcc++-10 作为选项的 Ubuntu 20.04 中使用 -O3 -Wall -Wextra 进行编译时会发生这种情况。如果我没有通过任何优化标志,就不会有任何警告。另外:如果我把构造函数拿走,警告也会消失。

解决方法

问题似乎与 memset() 有某种关系:由于使用条件 (len != 0) 避免它不起作用,似乎编译器认识到 CharArray<0> 的起始地址的对象由 CharArray<3> 的初始化生成并对此发出警告。可以通过有条件地不使用 CharArray<3> 初始化 memset() 或专门化该类型来测试该理论,因为这会使警告生效:

CharArray() { if (len != 3) memset(data_,len); }

template <>
struct CharArray<3> {
  CharArray(): data_() { } 
  char data_[3];
};  

警告可能是虚假的。似乎在使用零大小数组的地址时,编译器已经“忘记”它是通过访问不同数组的成员产生的。避免警告的最简单方法似乎是正确初始化初始化列表中的 data 而根本不使用 memset()

template <unsigned len>
struct CharArray {
  CharArray(): data_() {}
  char data_[len];
};  
,

对零长度的类似 C 的数组做任何事情都是非常可疑的。包括在我看来甚至定义一个。

但是,您可以将构造函数专门化为不对零长度数组执行任何操作:

axios.interceptors.response.use((response) => {
    if (response.config.responseType === 'blob') {
      downloadFileAxios(response);
    }
    return response;
  },async (error) => {
    if (error.response.status === 401) {
      // 401 redirect
      const data = await axios.get('/api/openapi/login');
      if (data.data && data.data.url) {
        window.location.href = data.data.url;
      }
    } else {
      return Promise.reject(error);
    }
  });

为了甚至不定义零大小的数组(我认为这不应该成为您可能希望通过该类实现的任何障碍的障碍...),您必须专门化整个类。 (此添加归功于 Dietmar Kühl)