Я слышал высказывание, что программисты C++ должны избежать memset,
class ArrInit {
//! int a[1024] = { 0 };
int a[1024];
public:
ArrInit() { memset(a, 0, 1024 * sizeof(int)); }
};
так рассматривая код выше, если Вы не используете memset, как Вы могли сделать [1.. 1024] заполненный нулем? Что случилось с memset в C++?
спасибо.
Проблема не столько в использовании memset() на встроенных типах, сколько в использовании их на типах классов (так называемых не-POD). Это почти всегда приводит к ошибкам и часто к роковым последствиям - например, к растоптанию указателя на таблицу виртуальной функции.
.В C++ std::fill
или std::fill_n
может быть лучшим выбором, так как он является универсальным и поэтому может работать как с объектами, так и с POD. Однако, memset
работает с необработанной последовательностью байт, и поэтому никогда не должен использоваться для инициализации не POD. Независимо от этого, оптимизированные реализации std::fill
могут внутренне использовать специализацию для вызова memset
, если тип POD.
Нулевая инициализация должна выглядеть следующим образом:
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, и нуль-инициализация уже должна быть доступна так или иначе.
Что не так с memset
в Си++ - это в основном то же самое, что не так с memset
в Си. memset
заполняет область памяти физическим нулевым разрядом, в то время как в реальности практически в 100% случаев требуется заполнение массива логическими нулевыми значениями соответствующего типа. В языке Си memset
гарантированно корректно инициализирует память только для целочисленных типов (и его корректная инициализация для всех целочисленных типов, в отличие от простого char-типа, является относительно недавней гарантией, добавленной в спецификацию языка Си). Не гарантируется правильное обнуление любых значений с плавающей точкой, не гарантируется получение правильных нулевых указателей.
Конечно, вышесказанное можно рассматривать как излишне педантичное, так как дополнительные стандарты и конвенции, действующие на данной платформе, могут (и, скорее всего, будут) расширить применимость memset
, но я бы все-таки предложил следовать принципу бритвы Оккама здесь: не полагайтесь на другие стандарты и конвенции, если только вам действительно не придется. Язык C++ (также как и C) предлагает несколько возможностей на уровне языка, которые позволяют безопасно инициализировать ваши совокупные объекты с правильными нулевыми значениями нужного типа. В других ответах уже упоминались эти возможности.
Это "плохо", потому что вы не реализуете свои намерения.
Ваше намерение - установить каждое значение в массиве в ноль, а то, что вы запрограммировали - это установка области необработанной памяти в ноль. Да, эти две вещи имеют один и тот же эффект, но понятнее просто написать код обнуления каждого элемента.
Также, скорее всего, это не более эффективно.
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.
. Ваш код в порядке. Я думал, что в Си++, где memset опасен, единственный раз, когда вы делаете что-то типа:
YourClass instance; memset(&instance, 0, sizeof(YourClass);
.
Я думаю, что это может обнулить внутренние данные в вашем экземпляре, которые создал компилятор.
.В 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.
.Помимо плохого при применении к классам, memset
также подвержен ошибкам. Очень легко вывести аргументы из строя или забыть о размере
части. Как правило, код скомпилируется с этими ошибками и спокойно делает неправильные вещи. Симптом ошибки может проявиться только много позже, что затруднит её отслеживание.
memset
также проблематичен с множеством простых типов, таких как указатели и плавающие точки. Некоторые программисты устанавливают все байты в 0, предполагая, что тогда указатели будут NULL, а плавающие - 0.0. Это не переносное предположение.
Нет никаких реальных причин не использовать его, за исключением нескольких случаев, когда люди указывали, что никто не будет использовать его в любом случае, но нет никакой реальной пользы от его использования, если только вы не заполняете мемгарды или что-то в этом роде.