Эффективное обнуление части массива

#c #arrays #memset

#c #массивы #memset

Вопрос:

Я пытаюсь сбросить массив простых объектов до нуля, как показано ниже:

 using Node = std::array<std::uint16_t, 256>;
using Array = std::array<Node, 4096>;

void reset_1(Arrayamp; a) {
  std::fill(std::begin(a), std::end(a), Node{});
}
    
void reset_2(Arrayamp; a) {
  a = {};
}

void reset_3(Arrayamp; a) {
  a.fill(Node{});
}
  

Все эти функции выполняют свою работу, но, глядя на проводник компилятора (используя как Clang 10.0.1, так и gcc 10.2), reset_2 он выполняет это в одном memset , в то время как два других выполняют это несколькими порциями (с тем же результатом std::fill_n ).

В моем реальном случае я действительно хочу сбросить все узлы, кроме первого. Это заставило бы меня написать что-то вроде:

 void reset_not_zero_1(Arrayamp; a) {
  std::fill(std::begin(a)   1, std::end(a), Node{});
}
    
void reset_not_zero_2(Arrayamp; a) {
  std::memset(a.data()   1, 0, sizeof(a) - sizeof(Node));
}
  

Мой вопрос: я вызываю какое-то неопределенное поведение с reset_not_zero_2 помощью? Массив легко копируется, но, возможно, я что-то упускаю.

Комментарии:

1. я бы также рассмотрел этот вариант: сохранить первый элемент temp , сбросить весь массив, назначить temp первому элементу.

2. Это нормально. std::array это просто массив в стиле c, завернутый в объекты, чтобы добавить к нему всю семантику копирования и другие удобные вещи.

Ответ №1:

Из cppreference

Преобразует значение ch в unsigned char и копирует его в каждый из первых символов count объекта, на который указывает dest . Если объект является потенциально перекрывающимся подобъектом или не поддается тривиальному копированию (например, скалярная, C-совместимая структура или массив тривиально копируемого типа), поведение не определено. Если count больше размера объекта, на который указывает dest, поведение не определено.

Пока все хорошо.

предположим, что размер другого узла равен 14, но его выравнивание равно 16

 using Array = std::array<Node, 4096>;
  

тогда размер ‘a’ будет равен 16 * 4096 = 65536, поскольку узел принудительно добавит 2.

 std::memset(a.data()   1, 0, sizeof(a) - sizeof(Node));
  

Тогда это будет 65536-14 из 65536-16 байт, что не нормально, поскольку оно перезаписывает 2 байта следующих данных.

Если, с другой стороны

 sizeof(a) == a.size()*sizeof(a::value_type) 
  

Тогда все должно быть в порядке.

Также, если выравнивание принудительно увеличивает размер, оно также должно быть в порядке.

Комментарии:

1. sizeof всегда содержит дополнение.

2. @eerorika разве это не только для внутреннего заполнения? в отличие от заполнения, принудительного выравнивания.

3. Если выравнивание узла равно 16, то начало двух элементов массива узла находится на расстоянии 16 байт друг от друга. Это расстояние по определению является тем, что возвращает sizeof.

4. @eerorika Я не вижу никакого влияния на sizeof для выравнивания на en.cppreference.com/w/cpp/language/object#Alignment но мой компилятор возвращает sizeof в качестве выравнивания, если оно более строгое, чем члены.

Ответ №2:

Мой вопрос: вызываю ли я какое-то неопределенное поведение с помощью reset_not_zero_2?

Нет. Мне кажется, все в порядке.