Подтвердить что ты не робот

Должен ли программист на С++ избегать memset?

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

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

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

спасибо.

4b9b3361

Ответ 1

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

Ответ 2

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

Ответ 3

Нулевая инициализация должна выглядеть так:

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, и нулевая инициализация уже должна быть доступна так или иначе.

Ответ 4

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

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

Кроме того, он, вероятно, не эффективнее.

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

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


int main()
{
    ArrInit a;
}

Компиляция с визуальным С++ 2008 32 бит с включенными оптимизациями компилирует цикл в -

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

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

Ответ 5

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

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

Ответ 6

Это OLD-поток, но здесь интересный поворот:

class myclass
{
  virtual void somefunc();
};

myclass onemyclass;

memset(&onemyclass,0,sizeof(myclass));

работает ОТЛИЧНО хорошо!

Однако

myclass *myptr;

myptr=&onemyclass;

memset(myptr,0,sizeof(myclass));

действительно устанавливает виртуальные (например, somefunc()) в NULL.

Учитывая, что memset значительно быстрее, чем установка 0 каждого и каждого члена в большом классе, я делал первый memset выше на века и никогда не испытывал проблем.

Итак, действительно интересный вопрос: как это работает? Я полагаю, что компилятор действительно начинает устанавливать нулевую ВЕСТЬ виртуальную таблицу... любую идею?

Ответ 7

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

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

Ответ 8

В дополнение к плохому при применении к классам, memset также подвержен ошибкам. Это очень просто, чтобы вывести аргументы из строя или забыть часть sizeof. Код, как правило, скомпилируется с этими ошибками и тихо поступает неправильно. Симптом ошибки может проявляться не намного позже, что затрудняет отслеживание.

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

Ответ 9

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

Ответ 10

Короткий ответ будет заключаться в использовании std::vector с начальным размером 1024.

std::vector< int > a( 1024 ); // Uses the types default constructor, "T()".

Начальное значение всех элементов "a" будет равно 0, так как конструктор std::vector (size) (а также vector:: resize) копирует значение конструктора по умолчанию для всех элементов. Для встроенных типов (встроенные типы a.k.a. или POD) вам гарантировано, что начальное значение будет 0:

int x = int(); // x == 0

Это позволит использовать тип, который "а" использует для изменения с минимальной суматохой, даже с классом.

Большинство функций, которые принимают указатель void (void *) в качестве параметра, например memset, не являются безопасными для типов. Игнорируя тип объекта, таким образом, удаляются все объекты семантики стиля С++, которые, как правило, зависят от конструкции, уничтожения и копирования. memset делает предположения о классе, который нарушает абстракцию (не зная и не заботясь о том, что находится внутри класса). Хотя это нарушение не всегда сразу становится очевидным, особенно с собственными типами, это может потенциально привести к затруднению поиска ошибок, особенно по мере роста базы кода и изменения рук. Если тип memset - это класс с виртуальной функцией vtable, он также перезапишет эти данные.

Ответ 11

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

Рассмотрим это:

class A {
    int i;

    A() : i(5) {}
}

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

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

 A a = new A[10];

чем каждый элемент в массиве будет иметь свой конструктор, а я будет установлен в 5.