Разница в инициализации и обнулении массива в c / c ++? - PullRequest
16 голосов
/ 17 января 2009

В c (или, может быть, c ++) какая разница между

char myarr[16]={0x00};

и

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

??

edit: я спрашиваю это, потому что в vc ++ 2005 результат тот же ..
редактировать больше: а также

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

Ответы [ 6 ]

18 голосов
/ 17 января 2009

Важным отличием является то, что первое значение по умолчанию инициализирует массив специфичным для элемента способом: указатели получат значение нулевого указателя , которое не должно быть 0x00 (как в случае всех битов ноль), логические значения будут false . Если тип элемента является типом класса, который не является так называемым 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

Кровавые подробности из C ++ Standard. Первый случай выше будет инициализировать оставшиеся элементы по умолчанию.

8.5

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

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

По умолчанию инициализировать объект типа T означает:

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

8.5.1

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

16 голосов
/ 17 января 2009

ИСО / МЭК 9899: ТС3 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);
4 голосов
/ 17 января 2009

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

3 голосов
/ 18 января 2009

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

Почти так же.

1 голос
/ 10 мая 2009

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

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

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

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

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

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

0 голосов
/ 18 января 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...