Разница в инициализации и обнуление массива в c/С++?

В c (или, возможно, С++), какая разница между

char myarr[16]={0x00};

и

char myarr[16];
memset(myarr, '\0', sizeof(myarr));

??

edit: Я спрашиваю об этом, потому что в vС++ 2005 результат такой же..
изменить еще: а также

char myarr[16]={0x00,}; 
?
возможно, может получить более полный ответ, а не двусмысленный, поскольку некоторые ответы ниже относятся к этому типу кода, т.е. поместите запятую перед закрытием фигурных скобок. Также результат аналогичен в vС++ 2005.

Ответ 1

Важное различие заключается в том, что первый по умолчанию инициализирует массив в зависимости от элемента: указатели получат значение нулевого указателя, которое не обязательно должно быть 0x00 (как и во всех битах-ноль), booleans будет ложный. Если тип элемента - это тип класса, который не является так называемым POD (простой старый тип данных), вы можете сделать только первый, потому что второй работает только для простейших случаев (где у вас нет виртуального функции, определяемые пользователем конструкторы и т.д.). Напротив, второй способ, использующий memset, устанавливает все элементы массива на все биты-ноль. Это не всегда то, что вы хотите. Если у вашего массива есть указатели, например, они не будут установлены нулевыми указателями.

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

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

<суб > Вот ассемблерный код, сгенерированный для первого случая. Мой gcc-материал не очень оптимизирован, поэтому мы получили реальный вызов memset (16 байтов на вершине стека всегда выделяются, даже если у нас нет локалей. $N - номер регистра):

void f(void) {
    int a[16] = { 42 };
}

sub     $29, $29, 88 ; create stack-frame, 88 bytes
stw     $31, $29, 84 ; save return address
add     $4, $29, 16  ; 1st argument is destination, the array.
add     $5, $0, 0    ; 2nd argument is value to fill
add     $6, $0, 64   ; 3rd argument is size to fill: 4byte * 16
jal     memset       ; call memset
add     $2, $0, 42   ; set first element, a[0], to 42
stw     $2, $29, 16  ;
ldw     $31, $29, 84 ; restore return address
add     $29, $29, 88 ; destroy stack-frame
jr      $31          ; return to caller

суб >

Сведения о gory из стандарта С++. В первом случае выше будет инициализироваться оставшийся элемент по умолчанию.

8.5:

Для нулевой инициализации хранилища для объекта типа T означает:

  • если T является скалярным типом, для хранилища установлено значение 0 (ноль), преобразованное в T;
  • если T - тип неединичного класса, хранилище для каждого нестатического элемента данных и каждый подобъект базового класса инициализируется нулем;
  • если T - тип объединения, хранилище для его первого элемента данных инициализируется нулем;
  • если T - тип массива, память для каждого элемента инициализируется нулем;
  • если T является ссылочным типом, инициализация не выполняется.

Для инициализации объекта типа T по умолчанию:

  • если T - тип класса, отличного от POD, конструктор по умолчанию для T называется
  • если T - тип массива, каждый элемент инициализируется по умолчанию;
  • в противном случае хранилище для объекта инициализируется нулем.

8.5.1:

Если в списке меньше инициализаторов, чем членов в совокупности, то каждый элемент, явно не инициализированный, должен быть инициализирован по умолчанию (8.5).

Ответ 2

ISO/IEC 9899: TC3 6.7.8, пункт 21:

Если в списке, заключенном в скобках, меньше инициализаторов, чем в элементе или элементах совокупности или меньше символов в строковом литерале, используемом для инициализации массива с известным размером, чем в массиве, остальная часть агрегат должен быть инициализирован неявным образом так же, как объекты, имеющие статическую продолжительность хранения.

Массивы со статическим временем хранения инициализируются до 0, поэтому спецификация C99 гарантирует, что явно не инициализированные элементы массива не будут установлены на 0.


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

#define count(ARRAY) (sizeof(ARRAY)/sizeof(*ARRAY))

int foo[16];
memcpy(foo, ((int [count(foo)]){ 1, 2, 3 }), sizeof(foo));

С помощью некоторой макромагии и нестандартного оператора __typeof__ это может быть значительно сокращено:

#define set_array(ARRAY, ...) \
    memcpy(ARRAY, ((__typeof__(ARRAY)){ __VA_ARGS__ }), sizeof(ARRAY))

int foo[16];
set_array(foo, 1, 2, 3);

Ответ 3

Возможно, char myarr[16]={0x00}; не является хорошим примером для начала, поскольку как явные, так и неявные инициализации членов используют нули, что затрудняет объяснение того, что происходит в этой ситуации. Я думал, что реальный пример с ненулевыми значениями может быть более наглядным:

/**
 * Map of characters allowed in a URL
 *
 * !, \, (, ), *, -, ., 0-9, A-Z, _, a-z, ~
 *
 * Allowed characters are set to non-zero (themselves, for easier tracking)
 */
static const char ALLOWED_IN_URL[256] = {
/*          0      1      2      3      4      5      6      7      8      9*/
/*   0 */   0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
/*  10 */   0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
/*  20 */   0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
/*  30 */   0,     0,     0,    '!',    0,     0,     0,     0,     0,   '\'',
/*  40 */  '(',   ')',   '*',    0,     0,    '-',   '.',    0,    '0',   '1',
/*  50 */  '2',   '3',   '4',   '5',   '6',   '7',   '8',   '9',    0,     0,
/*  60 */   0,     0,     0,     0,     0,    'A',   'B',   'C',   'D',   'E',
/*  70 */  'F',   'G',   'H',   'I',   'J',   'K',   'L',   'M',   'N',   'O',
/*  80 */  'P',   'Q',   'R',   'S',   'T',   'U',   'V',   'W',   'X',   'Y',
/*  90 */  'Z',    0,     0,     0,     0,    '_',    0,    'a',   'b',   'c',
/* 100 */  'd',   'e',   'f',   'g' ,  'h',   'i',   'j',   'k',   'l',   'm',
/* 110 */  'n',   'o',   'p',   'q',   'r',   's',   't',   'u',   'v',   'w',
/* 120 */  'x',   'y',   'z',    0,     0,     0,    '~',
};

Это таблица поиска, которая может использоваться, когда URL-кодировка строки. Только символы, разрешенные в URL-адресе, устанавливаются в ненулевое значение. Нуль означает, что символ не разрешен и должен быть закодирован в URL (%xx). Обратите внимание, что таблица резко заканчивается запятой после символа тильды. Ни один из символов, следующих за тильдой, не допускается и поэтому должен быть установлен на ноль. Но вместо того, чтобы писать еще много нулей, чтобы заполнить таблицу до 256 записей, мы позволяем компилятору неявно инициализировать остальные записи до нуля.

Ответ 4

Учитывая непростой факт, что = { 0 } является бесконечно читаемым, чем memset(..., ..., ... sizeof ...), тогда следующее будет препятствовать явным образом использовать memset:

В Visual Studio 2005, компиляция для Windows Mobile, полная оптимизированная версия сборки:

; DWORD a[10] = { 0 };

mov         r3, #0
mov         r2, #0x24
mov         r1, #0
add         r0, sp, #4
str         r3, [sp]
bl          memset
add         r4, sp, #0
mov         r5, #0xA

; DWORD b[10];
; memset(b, 0, sizeof(b));

mov         r2, #0x28
mov         r1, #0
add         r0, sp, #0x28
bl          memset
add         r4, sp, #0x28
mov         r5, #0xA

В значительной степени то же самое.

Ответ 5

Определение начальных значений в объявлении переменной происходит в другом месте, чем использование memset.

В первом случае нули определяются в некотором виде в двоичном виде как нулевая память инициализации (или ненулевая в зависимости от того, что вы инициализируете), и вы надеетесь, что загрузчик по достоинству оценит это, АБСОЛЮТНО не имеет ничего общего с C языковые стандарты. Последнее, используя memset, зависит от библиотеки C, которую вы также будете работать. Я больше верю в библиотеку.

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

Для стандартных операционных систем, Linux, Windows и т.д. init во время объявления переменной отлично, вы получите незаметное увеличение производительности, но если вы используете операционную систему, вы находитесь на платформе, которая достаточно быстро, чтобы не видеть, что разница.

В зависимости от двоичного типа первый случай init во время объявления может сделать двоичный файл более крупным. Это очень легко проверить. Скомпилируйте свой двоичный файл, как указано выше, затем измените размер массива с [16] на [16000], затем скомпилируйте его снова. Затем скомпилируйте без = {0x00} и сравните три бинарных размера.

Для большинства систем, которые большинство программистов когда-либо будут видеть, функциональных различий нет. Я рекомендую memset как привычку. Несмотря на то, что стандарты говорят, что многие, если не большинство компиляторов C (которых большинство программистов никогда не увидит в своей карьере), не будут похожи на этот init, поскольку количество элементов не соответствует размеру. Большинство компиляторов не соответствуют стандартам, даже если они требуют. Вместо этого создавайте хорошие привычки, которые избегают ярлыков или почти ничего, что должно работать на стандартном X, но отличается от прежнего стандарта M. (Избегайте компилятора gee whiz или стандартных трюков).

Ответ 6

Практически они одинаковы. Первая форма гарантирована, чтобы весь тип был равен 0x00 (например, пространство между элементами структуры), и это определено с C90. К сожалению, gcc дает предупреждение для первой формы с параметром -Wmissing-field-initializers. Подробнее здесь:

http://www.pixelbeat.org/programming/gcc/auto_init.html