Программист на C++ должен избежать memset?

Я слышал высказывание, что программисты C++ должны избежать memset,

class ArrInit {
    //! int a[1024] = { 0 };
    int a[1024];
public:
    ArrInit() {  memset(a, 0, 1024 * sizeof(int)); }
};

так рассматривая код выше, если Вы не используете memset, как Вы могли сделать [1.. 1024] заполненный нулем? Что случилось с memset в C++?

спасибо.

36
задан Jichao 29 December 2009 в 17:52
поделиться

9 ответов

Проблема не столько в использовании memset() на встроенных типах, сколько в использовании их на типах классов (так называемых не-POD). Это почти всегда приводит к ошибкам и часто к роковым последствиям - например, к растоптанию указателя на таблицу виртуальной функции.

.
44
ответ дан 27 November 2019 в 05:11
поделиться

В C++ std::fill или std::fill_n может быть лучшим выбором, так как он является универсальным и поэтому может работать как с объектами, так и с POD. Однако, memset работает с необработанной последовательностью байт, и поэтому никогда не должен использоваться для инициализации не POD. Независимо от этого, оптимизированные реализации std::fill могут внутренне использовать специализацию для вызова memset, если тип POD.

.
49
ответ дан 27 November 2019 в 05:11
поделиться

Нулевая инициализация должна выглядеть следующим образом:

class ArrInit {
    int a[1024];
public:
    ArrInit(): a() { }
};

Что касается использования memset, то есть несколько способов сделать использование более надежным (как и во всех подобных функциях): избегать жесткого кодирования размера и типа массива:

memset(a, 0, sizeof(a));

Для дополнительной проверки времени компиляции можно также убедиться, что a действительно является массивом (так что sizeof(a) будет иметь смысл):

template <class T, size_t N>
size_t array_bytes(const T (&)[N])  //accepts only real arrays
{
    return sizeof(T) * N;
}

ArrInit() { memset(a, 0, array_bytes(a)); }

Но для несимвольных типов, я представляю себе, что единственное значение, которое вы будете использовать для заполнения - это 0, и нуль-инициализация уже должна быть доступна так или иначе.

23
ответ дан 27 November 2019 в 05:11
поделиться

Что не так с memset в Си++ - это в основном то же самое, что не так с memset в Си. memset заполняет область памяти физическим нулевым разрядом, в то время как в реальности практически в 100% случаев требуется заполнение массива логическими нулевыми значениями соответствующего типа. В языке Си memset гарантированно корректно инициализирует память только для целочисленных типов (и его корректная инициализация для всех целочисленных типов, в отличие от простого char-типа, является относительно недавней гарантией, добавленной в спецификацию языка Си). Не гарантируется правильное обнуление любых значений с плавающей точкой, не гарантируется получение правильных нулевых указателей.

Конечно, вышесказанное можно рассматривать как излишне педантичное, так как дополнительные стандарты и конвенции, действующие на данной платформе, могут (и, скорее всего, будут) расширить применимость memset, но я бы все-таки предложил следовать принципу бритвы Оккама здесь: не полагайтесь на другие стандарты и конвенции, если только вам действительно не придется. Язык C++ (также как и C) предлагает несколько возможностей на уровне языка, которые позволяют безопасно инициализировать ваши совокупные объекты с правильными нулевыми значениями нужного типа. В других ответах уже упоминались эти возможности.

.
10
ответ дан 27 November 2019 в 05:11
поделиться

Это "плохо", потому что вы не реализуете свои намерения.

Ваше намерение - установить каждое значение в массиве в ноль, а то, что вы запрограммировали - это установка области необработанной памяти в ноль. Да, эти две вещи имеют один и тот же эффект, но понятнее просто написать код обнуления каждого элемента.

Также, скорее всего, это не более эффективно.

class ArrInit
{
public:
    ArrInit();
private:
    int a[1024];
};

ArrInit::ArrInit()
{
    for(int i = 0; i < 1024; ++i) {
        a[i] = 0;
    }
}


int main()
{
    ArrInit a;
}

Компиляция этого с визуальным c++ 2008 32 bit с оптимизациями, включившими компиляцию цикла до -

; Line 12
    xor eax, eax
    mov ecx, 1024               ; 00000400H
    mov edi, edx
    rep stosd

Что в любом случае является именно тем, к чему, скорее всего, скомпилировал бы memset. Но если Вы используете memset, то у компилятора нет никаких возможностей для дальнейших оптимизаций, в то время как при написании статьи возможно, что компилятор мог бы выполнить дальнейшие оптимизации, например, заметив, что каждый элемент перед использованием устанавливается на что-то другое, чтобы можно было оптимизировать инициализацию, что он, скорее всего, не смог бы сделать почти так же легко, если бы Вы использовали memset.

.
8
ответ дан 27 November 2019 в 05:11
поделиться

Ваш код в порядке. Я думал, что в Си++, где memset опасен, единственный раз, когда вы делаете что-то типа:
YourClass instance; memset(&instance, 0, sizeof(YourClass);.

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

.
0
ответ дан 27 November 2019 в 05:11
поделиться

В C++ вы должны использовать новый. В случае с простыми массивами, как в вашем примере, нет никаких реальных проблем с его использованием. Однако, Если у вас есть массив классов и вы использовали memset для его инициализации, то вы не будете правильно строить классы.

Рассмотрим следующее:

class A {
    int i;

    A() : i(5) {}
}

int main() {
    A a[10];
    memset (a, 0, 10 * sizeof (A));
}

Для каждого из этих элементов будет вызван конструктор а не, поэтому переменная-член i не будет установлена в 5. Если вы использовали new вместо этого:

 A a = new A[10];

то для каждого элемента массива будет вызван его конструктор и i будет установлено в 5.

.
-4
ответ дан 27 November 2019 в 05:11
поделиться

Помимо плохого при применении к классам, memset также подвержен ошибкам. Очень легко вывести аргументы из строя или забыть о размере части. Как правило, код скомпилируется с этими ошибками и спокойно делает неправильные вещи. Симптом ошибки может проявиться только много позже, что затруднит её отслеживание.

memset также проблематичен с множеством простых типов, таких как указатели и плавающие точки. Некоторые программисты устанавливают все байты в 0, предполагая, что тогда указатели будут NULL, а плавающие - 0.0. Это не переносное предположение.

0
ответ дан 27 November 2019 в 05:11
поделиться

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

0
ответ дан 27 November 2019 в 05:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: