Подтвердить что ты не робот

Почему встроенные массивы не безопасны?

Книга С++ Primer, 5th edition от Стэнли Б. Липпман (ISBN 0-321-71411-3/978-0-321-71411-4) упоминает:

An [std::]array - более безопасная, простая в использовании альтернатива встроенным массивам.

Что случилось со встроенными массивами?

4b9b3361

Ответ 1

  • Встроенный массив представляет собой непрерывный блок байтов, обычно в стеке. У вас действительно нет достойного способа сохранить полезную информацию о массиве, его границах или состоянии. std::array хранит эту информацию.

  • Встроенные массивы разлагаются в указатели при передаче из/в функции. Это может вызвать:

    • При передаче встроенного массива вы передаете необработанный указатель. Указатель не сохраняет информацию о размере массива. Вам придется пройти по размеру массива и, таким образом, убрать код. std::array может передаваться как ссылка, копировать или перемещаться.

    • Нет способа вернуть встроенный массив, вы в конце концов вернете указатель на локальную переменную, если массив был объявлен в этой области функции. std::array может быть возвращен безопасно, поскольку он автоматически управляет объектом и его временем жизни.

  • Вы не можете делать полезные вещи на встроенных массивах, таких как назначение, перемещение или копирование. Вы закончите писать настраиваемую функцию для каждого встроенного массива (возможно, используя шаблоны). std::array может быть назначено.

  • Получив доступ к элементу, который выходит за границы массива, вы запускаете поведение undefined. std::array::at выполнит предварительную проверку границ и выкинет регулярное исключение С++, если проверка завершится с ошибкой.

  • Улучшенная читаемость: встроенные массивы включают арифметику указателей. std::array реализует полезные функции, такие как front, back, begin и end, чтобы этого не было.

Скажем, я хочу сортировать встроенный массив, код может выглядеть так:

int arr[7] = {/*...*/};
std::sort(arr, arr+7);

Это не самый надежный код. Изменяя 7 на другое число, код прерывается.

С std::array:

std::array<int,7> arr{/*...*/};
std::sort(arr.begin(), arr.end());

Код более надежный и гибкий.

Просто, чтобы все было ясно, встроенные массивы иногда могут быть проще. Например, многие функции Windows, а также функции UNIX API/syscalls требуют, чтобы некоторые (маленькие) буферы заполняли данные. Я бы не пошел с накладными расходами std::array вместо простого char[MAX_PATH], который я могу использовать.

Ответ 2

Трудно оценить, что имел в виду автор, но я бы предположил, что они имеют в виду следующие факты о собственных массивах:

  • они сырые
    Нет функции-члена .at, которую вы можете использовать для доступа к элементу с проверкой границ, хотя я бы ответил, что вы, как правило, этого не хотите. Либо вы получаете доступ к элементу, который, как вы знаете, существует, либо выполняете итерацию (что вы можете одинаково хорошо использовать с std::array и встроенными массивами); если вы не знаете, что элемент существует, аксессуар проверки границ - это довольно плохой способ убедиться в том, что, поскольку он использует неправильный инструмент для потока кода, и он имеет существенное ограничение производительности.

  • они могут запутать
    Новички, как правило, забывают о распаде имени массива, передавая массивы в функции "по значению", а затем выполняя sizeof на следующем указателе; это обычно не "небезопасно", но оно создаст ошибки.

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

Предполагая, что автор рекомендует std::array, это было бы потому, что он "исправляет" все перечисленные выше вещи, что приводит к обычному улучшению кода по умолчанию.

Но являются ли родные массивы каким-то образом "небезопасными" по сравнению? Нет, я бы так не сказал.

Ответ 3

Как std::array безопаснее и проще в использовании, чем встроенный массив?

Легко запутаться со встроенными массивами, особенно для программистов, которые не являются экспертами на С++ и программистами, которые иногда делают ошибки. Это вызывает множество ошибок и уязвимостей безопасности.

  • С помощью std::array a1 вы можете получить доступ к элементу с проверкой границ a.at(i) или без проверки границ a[i]. Со встроенным массивом всегда требуется ваша обязанность усердно избегать доступа вне пределов. В противном случае код может разбить некоторую память, которая остается незамеченной в течение длительного времени и становится очень сложной для отладки. Даже просто чтение за пределами границ массива может быть использовано для уязвимостей безопасности, таких как ошибка Heartbleed, которая раскрывает частные ключи шифрования.
  • Учебники по С++ могут притворяться, что массив-из-T и указатель-to-T - это одно и то же, а затем рассказывать вам о различных исключениях, когда они не одно и то же. Например. массив структуры в структуре встроен в структуру, а указатель-в-в структуре - это указатель на память, который лучше выделить. Или рассмотрите массив массивов (например, растровое изображение). Автоматически увеличивает указатель на следующий пиксель или следующую строку? Или рассмотрите массив объектов, где указатель объекта принуждает его указатель на базовый класс. Все это сложно, и компилятор не ошибается.
  • С помощью std::array a1 вы можете получить свой размер a1.size(), сравнить его содержимое с другим std:: array a1 == a2 и использовать другие стандартные методы контейнера, такие как a1.swap(a2). Благодаря встроенным массивам эти операции занимают больше работы по программированию и их легче испортить. Например. int b1[] = {10, 20, 30};, чтобы получить его размер без жесткого кодирования 3, вы должны сделать sizeof(b1) / sizeof(b1[0]). Чтобы сравнить его содержимое, вы должны перебрать эти элементы.
  • Вы можете передать std:: array в функцию по ссылке f(&a1) или по значению f(a1) [i.e. по копиям]. Передача встроенного массива осуществляется только по ссылке и смешивает его с указателем на первый элемент. Это не то же самое. Компилятор не передает размер массива.
  • Вы можете вернуть std:: array из функции по значению, return a1. Возврат встроенного массива return b1 возвращает оборванный указатель, который сломан.
  • Вы можете скопировать std:: array обычным способом, a1 = a2, даже если он содержит объекты с конструкторами. Если вы попробуете это со встроенными массивами, b1 = b2, он просто скопирует указатель массива (или не скомпилируется, в зависимости от того, как объявлен b2). Вы можете обойти это с помощью memcpy(b1, b2, sizeof(b1) / sizeof(b1[0])), но это будет нарушено, если массивы имеют разные размеры или содержат элементы с конструкторами.
  • Вы можете легко изменить код, который использует std:: array для использования другого контейнера, такого как std::vector или std:: map.

Смотрите С++ FAQ Почему я должен использовать классы контейнеров, а не простые массивы?, чтобы узнать больше, например. опасности встроенных массивов, содержащих объекты С++ с деструкторами (например, std::string) или наследование.

Не беспокоить производительность

При проверке доступа a1.at(i) требуется еще несколько инструкций каждый раз при извлечении или хранении элемента массива. В некотором внутреннем цикле кода, который застревает через большой массив (например, подпрограмму обработки изображений, которую вы вызываете на каждом видеокадре), эта стоимость может складываться достаточно, чтобы иметь значение. В этом редком случае имеет смысл использовать непроверенный доступ a[i] и тщательно следить за тем, чтобы код цикла заботился о границах.

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

Ответ 4

Единственное преимущество встроенного массива - немного более сжатый синтаксис объявления. Но функциональные преимущества std::array выдувают из воды. Я бы также добавил, что это действительно не так важно. Если вам нужно поддерживать старые компиляторы, тогда у вас нет выбора, конечно, поскольку std::array предназначен только для С++ 11. В противном случае вы можете использовать то, что вам нравится, но если вы не делаете только тривиальное использование массива, вы должны предпочесть std:: array, чтобы поддерживать совместимость с другими контейнерами STL (например, что, если позже вы решите сделать динамический размер, и вместо этого используйте std::vector, тогда вы будете счастливы, что вы использовали std::array, потому что все, что вам нужно будет изменить, вероятно, это объявление самого массива, а остальное будет таким же, особенно если вы используете авто и другие типы, функции вывода С++ 11.

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

Массивы используются для хранения последовательности объектов Проверьте учебник: http://www.cplusplus.com/doc/tutorial/arrays/

A std::vector делает то же самое, но лучше, чем встроенные массивы (например, в общем случае вектор не имеет большой разницы в эффективности, чем встроенный в массивах при доступе к элементам через оператор []): http://www.cplusplus.com/reference/stl/vector/

Встроенные массивы являются основным источником ошибок, особенно когда они используются для построения многомерных массивов. Для новичков они также являются основным источником путаницы. По возможности используйте вектор, список, valarray, строку и т.д. Контейнеры STL не имеют таких же проблем, как встроенные в массивы

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

Если OP действительно хочет массив, С++ 11 предоставляет оболочку для встроенного массива std:: array. Использование std:: array очень похоже на использование встроенного массива, не влияет на их производительность во время выполнения, с гораздо большим количеством функций.

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

Во всяком случае, встроенные массивы - все пути, переданные по ссылке. Причиной этого является передача массива функции в качестве аргумента, указатель на который передается первый элемент.

когда вы скажете void f(T[] array), компилятор превратит его в void f(T* array) Когда дело доходит до строк. Строки в стиле C (т.е. последовательности символов с нулевым завершением) - это все пути, переданные по ссылке, так как они также являются массивами "char".

Строки STL по умолчанию не передаются по ссылке. Они действуют как обычные переменные. Не существует предопределенных правил для передачи параметров по ссылке. Несмотря на то, что массивы всегда передаются по ссылке автоматически.


vector<vector<double>> G1=connectivity( current_combination,M,q2+1,P );
vector<vector<double>> G2=connectivity( circshift_1_dexia(current_combination),M,q1+1,P );

Это также может быть копирование векторов, поскольку связь возвращает вектор по значению. В некоторых случаях компилятор будет оптимизировать это. Однако, чтобы этого избежать, вы можете передать вектор как неконстантную ссылку на подключение, а не возвращать их. Возвращаемое значение maxweight представляет собой трехмерный вектор, возвращаемый значением (который может сделать его копию). Векторы эффективны только для вставки или стирания в конце, и лучше всего вызвать reserve(), если вы собираетесь push_back много значений. Возможно, вы сможете переписать его с помощью списка, если вам действительно не нужен произвольный доступ; со списком вы теряете оператор индексирования, но вы все равно можете выполнять линейные проходы и сохранять итераторы для элементов, а не индексы.

С некоторыми компиляторами можно быстрее использовать pre-increment, а не post-increment. Предпочитайте ++ i-i ++, если вам действительно не нужно использовать пост-инкремент. Они не совпадают.

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

Ответ 5

std:: array имеет функцию члена at, которая безопасна. Он также имеет begin, end, size, который вы можете использовать, чтобы сделать ваш код более безопасным.

Необработанные массивы этого не имеют. (В частности, когда сырые массивы разлагаются на указатели, например, когда они передаются как аргументы, вы теряете информацию о размере, которая хранится в типе std::array, так как это шаблон с размером в качестве аргумента)

И хороший оптимизирующий компилятор С++ 11 будет обрабатывать std::array (или ссылки на них) так же эффективно, как и необработанные массивы.

Ответ 6

Встроенные массивы не являются по своей сути небезопасными - если они используются правильно. Но проще использовать встроенные массивы неправильно, чем использовать альтернативы, такие как std::array, неправильно, и эти альтернативы обычно предлагают улучшенные функции отладки, которые помогут вам обнаружить, когда они были использованы неправильно.

Ответ 7

Встроенные массивы тонкие. Существует множество аспектов, которые ведут себя неожиданно, даже опытным программистам.

std::array<T, N> действительно является оберткой вокруг T[N], но многие из уже упомянутых аспектов сортируются бесплатно по существу, что является очень оптимальным и тем, что вы хотите иметь.

Вот некоторые из них я не читал:

  • Размер: N должен быть постоянным выражением, он не может быть переменным для обоих. Однако, со встроенными массивами, есть VLA (Variable Length Array), который также позволяет это.

    Официально только C99 поддерживает их. Тем не менее, многие компиляторы допускают, что в предыдущих версиях C и С++ в качестве расширений. Следовательно, у вас может быть

    int n; std::cin >> n;
    int array[n]; // ill-formed but often accepted
    

    что компилируется отлично. Если бы вы использовали std::array, этот никогда не мог работать, потому что N требуется и проверяется как фактическое постоянное выражение!

  • Ожидания для массивов. Общим недостатком является то, что размер массива переносится вместе с самим массивом, когда массив больше не является массивом из-за сохраненного вводящий в заблуждение синтаксис Си, как в:

    void foo(int array[])
    {
        // iterate over the elements
        for (int i = 0; i < sizeof(array); ++i)
             array[i] = 0;
    }
    

    но это неправильно, потому что array уже разложился на указатель, который не имеет информации о размере заостренной области. Это заблуждение вызывает поведение undefined, если array имеет меньше, чем sizeof(int*) элементов, обычно 8, за исключением логически ошибочных.

    1. Сумасшедший использует:. И в дальнейшем есть несколько массивов причуд:

      • Имеете ли вы массив [i] или я [array], нет никакой разницы. Это неверно для array, потому что вызов перегруженного оператора фактически является вызовом функции и имеет смысл порядок параметров.

      • Массивы с нулевым размером: N должны быть больше нуля, но по-прежнему разрешены как расширения и, как и прежде, часто не предупреждаются, если не требуется больше педантизма. Дополнительная информация здесь. array имеет другую семантику:

        Существует специальный случай для массива нулевой длины (N == 0). В этом case, array.begin() == array.end(), что является некоторым уникальным значением. эффект вызова front() или back() в массиве нулевого размера равен undefined.