Вызывает ли многомерный массив какие-либо проблемы в C и / или C ++? - PullRequest
1 голос
/ 03 февраля 2020

Я знаю, что этот вопрос кажется на первый взгляд немного смешным. Но когда я наткнулся на этот вопрос , я нашел комментарий , @ BasileStarynkevitch , C и пользователя высокого уровня C ++, в котором он утверждал, что многомерные массивы не должны быть предпочтительными для использования, ни в C, ни в C ++:

Не используйте многомерные массивы в C ++ (или в C).

Почему? Почему я не должен использовать многомерные массивы в C ++ или C?

Что он имел в виду под этим утверждением?


После этого другой пользователь ответил на этот комментарий:

Базиль прав. Можно объявить трехмерный массив в C / C ++, но это вызывает слишком много проблем.

Какие проблемы?

Я часто использую многомерные массивы и не вижу недостатков в их использовании , Напротив, я думаю, что у него есть только преимущества.

  • Есть ли какие-либо проблемы с использованием многомерных массивов, о которых я не знаю?

  • Может кто-нибудь объяснить мне, что они имели в виду?

Ответы [ 5 ]

5 голосов
/ 03 февраля 2020

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


C ++

Многомерные массивы довольно бесполезны в C ++, потому что вы не можете выделить их с динамическими размерами c. Размеры всех измерений, кроме самого внешнего, должны быть постоянными времени компиляции. Практически во всех случаях использования многомерных массивов, с которыми я сталкивался, параметры размера просто не известны во время компиляции. Поскольку они берутся из размеров файла изображения или какого-либо параметра моделирования и т. Д. c.

В некоторых особых случаях размеры могут быть известны во время компиляции, и в этих случаях нет проблем с использованием многомерных массивов в C ++. Во всех остальных случаях вам нужно либо использовать массивы указателей (утомительно настраивать), вложенный std::vector<std::vector<std::vector<...>>>, либо одномерный массив с ручным вычислением индекса (подвержен ошибкам).


C

C допускает истинные многомерные массивы с динамическими размерами c начиная с C99. Это называется VLA, и оно позволяет вам создавать полностью динамические многомерные массивы как в стеке, так и в куче.

Однако есть две ловушки:

  • You может передавать многомерный VLA в функцию, но вы не можете его вернуть. Если вы хотите передать многомерные данные из функции, вы должны вернуть их по ссылке.

    void foo(int width, int height, int (*data)[width]);  //works
    //int (*bar(int width, int height))[width];  //does not work
    
  • Вы можете иметь указатели на многомерные массивы в переменных и передавать их в функций, но вы не можете хранить их в структурах.

    struct foo {
        int width, height;
        //int (*data)[width];  //does not work
    };
    

Обе проблемы можно обойти (передать по ссылке, чтобы вернуть многомерный массив, и сохранить указатель как void* в структуре), но это не тривиально. И поскольку эта функция не используется широко, лишь немногие знают, как это сделать правильно.


Размеры массива времени компиляции

И C, и C ++ позволяют использовать многомерный массивы с размерами, известными во время компиляции. У них нет недостатков, перечисленных выше.

Но их полезность значительно снижается: очень много случаев, когда вы хотите использовать многомерный массив, и у вас нет шансов на шанс знать соответствующие размеры во время компиляции. Пример - обработка изображения: вы не знаете размеры изображения до того, как откроете файл изображения. Аналогично любому физическому моделированию: вы не знаете, насколько велик ваш рабочий домен, пока ваша программа не загрузит свои файлы конфигурации. Et c.

Итак, чтобы быть полезными, многомерные массивы должны поддерживать динамические c размеры imho.

4 голосов
/ 03 февраля 2020

Это довольно широкая (и интересная) тема, связанная с производительностью c. Мы могли бы обсудить пропуски в кеше, стоимость инициализации многомерных массивов, векторизацию, размещение многомерных std::array в стеке, распределение многомерных std::vector в куче, доступ к двум последним и т. Д. И т. Д.

Тем не менее, если ваша программа отлично работает с вашими многомерными массивами, оставьте все как есть, особенно если ваши многомерные массивы обеспечивают большую читабельность.

Пример, связанный с производительностью:

Рассмотрим std::vector, который содержит много std::vector<double>:

std::vector<std::vector<double>> v;

Мы знаем, что каждый std::vector объект внутри v расположен непрерывно. Кроме того, все элементы в std::vector<double> в v расположены непрерывно. Однако не все double, присутствующие в v, находятся в смежной памяти. Таким образом, в зависимости от того, как вы получаете доступ к этим элементам (сколько раз, в каком порядке, ...), std::vector из std::vector может быть очень медленным по сравнению с одним std::vector<double>, содержащим все double В непрерывной памяти.

Матричные библиотеки обычно хранят матрицу 5x5 в простом массиве размером 25.

2 голосов
/ 03 февраля 2020

Как и в большинстве структур данных, есть «правильное» время для их использования и «неправильное» время. Это в значительной степени субъективно, но для целей этого вопроса давайте просто предположим, что вы используете 2D-массив в месте, где это не имеет смысла.

Тем не менее, я думаю, что есть две заметные причины избегайте использования многомерных массивов в C ++, и они в основном возникают в зависимости от случаев использования массива. А именно:

1. Медленный (er) обход памяти

2-мерный массив, такой как i [j] [k], может быть доступен непрерывно, но компьютер должен тратить дополнительное время на вычисление адреса каждого элемента - больше чем это будет тратить на одномерный массив. Что еще более важно, итераторы теряют удобство использования в многомерных массивах, заставляя вас использовать нотацию [j] [k], которая медленнее. Одним из основных преимуществ простых массивов является их возможность последовательного доступа ко всем членам. Это частично потеряно с массивом 2 + D.

2. Негибкий размер

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

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

1 голос
/ 03 февраля 2020

Заявления применяются широко , но не универсально . Если у вас есть оценки c, это нормально.

В C ++, если вам нужны динамические c границы, вы не можете иметь одно смежное распределение, потому что измерения являются частью типа. Даже если вы не заботитесь о непрерывном распределении, вы должны быть особенно осторожны, особенно если вы хотите sh, чтобы изменить размер измерения.

Гораздо проще иметь в одном контейнере одно измерение, которое будет управлять распределением, и многомерное представление

Дано:

std::size_t N, M, L;
std::cin >> N >> M >> L;

Сравните:

int *** arr = new int**[N];
std::generate_n(arr, N, [M, L]()
{ 
    int ** sub = new int*[M];
    std::generate_n(sub, M, [L](){ return new int[L]; });
    return sub;
});

// use arr

std::for_each_n(arr, N, [M](int** sub)
{ 
    std::for_each_n(sub, M, [](int* subsub){ delete[] subsub; });
    delete[] sub;
});
delete[] arr;

С:

std::vector<int> vec(N * M * L);
gsl::multi_span arr(vec.data(), gsl::strided_bounds<3>({ N, M, L }));

// use arr
1 голос
/ 03 февраля 2020

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

Я часто использовал многомерные массивы для сложных матричных манипуляций в C и C ++. Это очень часто встречается в анализе сигналов и обнаружении сигналов, а также в высокопроизводительных библиотеках для анализа геометрии в симуляциях. Я даже не рассматривал динамическое распределение массива c как часть вопроса. Даже тогда массивы типичного размера для определенных связанных проблем с функцией сброса могут сохранить память и повысить производительность для сложного анализа. Можно использовать кэш для меньших матричных манипуляций в библиотеке и более сложную обработку C ++ OO для больших динамических распределений c для каждой проблемы.

...