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

16
задан Daniel Daranas 1 August 2013 в 16:46
поделиться

6 ответов

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

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

Второе делает 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

Окровавленные детали из Стандарта C++. Первый случай выше примет значение по умолчанию - инициализируют остающиеся элементы.

8.5:

Для обнуления - инициализируют устройство хранения данных для объекта средств типа T:

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

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

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

8.5.1:

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

18
ответ дан 30 November 2019 в 16:14
поделиться

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);
16
ответ дан 30 November 2019 в 16:14
поделиться

Возможно 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
ответ дан 30 November 2019 в 16:14
поделиться

Учитывая трудное к спору то, которое = { 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

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

3
ответ дан 30 November 2019 в 16:14
поделиться

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

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

0
ответ дан 30 November 2019 в 16:14
поделиться

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

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

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

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

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

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

1
ответ дан 30 November 2019 в 16:14
поделиться