Ответ 1
memset(array, 0, sizeof(array[0][0]) * m * n);
Где m
и n
- ширина и высота двумерного массива (в вашем примере у вас есть квадратный двумерный массив, поэтому m == n
).
Я хочу многократно обнулить большой массив 2d в C. Это то, что я делаю в данный момент:
// Array of size n * m, where n may not equal m
for(j = 0; j < n; j++)
{
for(i = 0; i < m; i++)
{
array[i][j] = 0;
}
}
Я попытался использовать memset:
memset(array, 0, sizeof(array))
Но это работает только для 1D массивов. Когда я печатаю содержимое 2D-массива, первая строка равна нулю, но затем я получаю нагрузку случайных больших чисел, и она падает.
memset(array, 0, sizeof(array[0][0]) * m * n);
Где m
и n
- ширина и высота двумерного массива (в вашем примере у вас есть квадратный двумерный массив, поэтому m == n
).
Если array
- действительно массив, то вы можете "обнулить его" с помощью:
memset(array, 0, sizeof array);
Но есть два момента, которые вы должны знать:
array
действительно представляет собой "двухмерный массив", т.е. был объявлен T array[M][N];
для некоторого типа T
.array
. Если вы передадите его функции, то имя array
распадается на указатель, а sizeof
не даст вам размер массива.Сделайте эксперимент:
#include <stdio.h>
void f(int (*arr)[5])
{
printf("f: sizeof arr: %zu\n", sizeof arr);
printf("f: sizeof arr[0]: %zu\n", sizeof arr[0]);
printf("f: sizeof arr[0][0]: %zu\n", sizeof arr[0][0]);
}
int main(void)
{
int arr[10][5];
printf("main: sizeof arr: %zu\n", sizeof arr);
printf("main: sizeof arr[0]: %zu\n", sizeof arr[0]);
printf("main: sizeof arr[0][0]: %zu\n\n", sizeof arr[0][0]);
f(arr);
return 0;
}
На моей машине вышеприведенные отпечатки:
main: sizeof arr: 200
main: sizeof arr[0]: 20
main: sizeof arr[0][0]: 4
f: sizeof arr: 8
f: sizeof arr[0]: 20
f: sizeof arr[0][0]: 4
Даже если arr
является массивом, он распадается на указатель на свой первый элемент при передаче на f()
, и поэтому размеры, напечатанные в f()
, являются "неправильными". Кроме того, в f()
размер arr[0]
представляет собой размер массива arr[0]
, который представляет собой "массив [5] of int
". Это не размер int *
, так как "распад" происходит только на первом уровне, поэтому нам нужно объявить f()
как указание указателя на массив правильного размера.
Итак, как я уже сказал, то, что вы делали изначально, будет работать только в том случае, если выполнены два вышеперечисленных условия. Если нет, вам нужно будет сделать то, что сказали другие:
memset(array, 0, n*n*sizeof array[0][0]);
Наконец, memset()
и цикл for
, который вы опубликовали, не являются эквивалентными в строгом смысле. Могут быть (и были) компиляторы, где "все биты нуль" не равны нулю для определенных типов, таких как указатели и значения с плавающей запятой. Я сомневаюсь, что вам нужно об этом беспокоиться.
Ну, самый быстрый способ сделать это - не делать этого вообще.
Звучит странно, я знаю, вот какой-то псевдокод:
int array [][];
bool array_is_empty;
void ClearArray ()
{
array_is_empty = true;
}
int ReadValue (int x, int y)
{
return array_is_empty ? 0 : array [x][y];
}
void SetValue (int x, int y, int value)
{
if (array_is_empty)
{
memset (array, 0, number of byte the array uses);
array_is_empty = false;
}
array [x][y] = value;
}
Фактически, он все еще очищает массив, но только когда что-то записывается в массив. Это не является большим преимуществом. Однако, если 2D-массив был реализован с использованием, скажем, квадратного дерева (а не динамического ума) или набора строк данных, тогда вы можете локализовать эффект булевского флага, но вам потребуется больше флагов. В квадратном дереве просто установите пустой флаг для корня node, в массиве строк просто установите флаг для каждой строки.
Что приводит к вопросу "почему вы хотите повторно обнулить большой массив 2d"? Для чего используется массив? Есть ли способ изменить код, чтобы массив не нуждался в обнулении?
Например, если у вас есть:
clear array
for each set of data
for each element in data set
array += element
то есть использовать его для буфера накопления, а затем изменить его, как это улучшило бы производительность без конца:
for set 0 and set 1
for each element in each set
array = element1 + element2
for remaining data sets
for each element in data set
array += element
Это не требует, чтобы массив был очищен, но все еще работает. И это будет намного быстрее, чем очистка массива. Как я уже сказал, самый быстрый способ - не делать этого в первую очередь.
Если вы действительно, действительно одержимы скоростью (и не столько с переносимостью), я думаю, что самым быстрым способом сделать это было бы использование внутренних векторов SIMD. например на процессорах Intel вы можете использовать эти инструкции SSE2:
__m128i _mm_setzero_si128 (); // Create a quadword with a value of 0.
void _mm_storeu_si128 (__m128i *p, __m128i a); // Write a quadword to the specified address.
Каждая команда хранения установит четыре 32-битных ints в ноль за один удар.
p должен быть выровнен по 16 байт, но это ограничение также полезно для скорости, поскольку оно поможет кешу. Другое ограничение состоит в том, что p должен указывать на размер распределения, который является кратным 16-байтам, но это тоже классно, потому что это позволяет нам легко развертывать цикл.
Сделайте это в цикле и разворачивайте цикл несколько раз, и у вас будет сумасшедший быстрый инициализатор:
// Assumes int is 32-bits.
const int mr = roundUpToNearestMultiple(m, 4); // This isn't the optimal modification of m and n, but done this way here for clarity.
const int nr = roundUpToNearestMultiple(n, 4);
int i = 0;
int array[mr][nr] __attribute__ ((aligned (16))); // GCC directive.
__m128i* px = (__m128i*)array;
const int incr = s >> 2; // Unroll it 4 times.
const __m128i zero128 = _mm_setzero_si128();
for(i = 0; i < s; i += incr)
{
_mm_storeu_si128(px++, zero128);
_mm_storeu_si128(px++, zero128);
_mm_storeu_si128(px++, zero128);
_mm_storeu_si128(px++, zero128);
}
Существует также вариант _mm_storeu
, который обходит кеш (т.е. обнуление массива не будет загрязнять кеш), что может привести к некоторым дополнительным преимуществам производительности при некоторых обстоятельствах.
См. здесь для справки SSE2: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx
Если вы инициализируете массив с помощью malloc
, используйте calloc
; он будет нулевым вашим массивом бесплатно. (Тот же перм, очевидно, как memset, просто меньше кода для вас.)
int array[N][M] = {0};
... по крайней мере в GCC 4.8.
Как был объявлен ваш 2D-массив?
Если это что-то вроде:
int arr[20][30];
Вы можете обнулить его, выполнив:
memset(arr, sizeof(int)*20*30);
Использовать calloc вместо malloc. calloc запустит все поля в 0.
int * a = (int *) calloc (n, размер (int));
//все ячейки a были инициализированы до 0
memset(array, 0, sizeof(int [n][n]));
Я думаю, что самый быстрый способ сделать это вручную - это следующий код. Вы можете сравнить скорость с функцией memset, но она не должна быть медленнее.
(изменить тип указателей ptr и ptr1, если ваш тип массива отличается от int)
#define SIZE_X 100
#define SIZE_Y 100
int *ptr, *ptr1;
ptr = &array[0][0];
ptr1 = ptr + SIZE_X*SIZE_Y*sizeof(array[0][0]);
while(ptr < ptr1)
{
*ptr++ = 0;
}
Вы можете попробовать это
int array[20,30] = {{0}};
Это происходит потому, что sizeof (array) дает вам размер выделения объекта, на который указывает массив. (массив - это просто указатель на первую строку вашего многомерного массива). Однако вы выделили j массивов размера i. Следовательно, вам нужно умножить размер одной строки, которая возвращается sizeof (array) с количеством выделенных строк, например:
bzero(array, sizeof(array) * j);
Также обратите внимание, что sizeof (array) будет работать только для статически распределенных массивов. Для динамически выделенного массива вы должны написать
size_t arrayByteSize = sizeof(int) * i * j;
int *array = malloc(array2dByteSite);
bzero(array, arrayByteSize);