Что послужило причиной отсутствия явного хранения длины массива в массиве C
?
На мой взгляд, есть веские причины для этого, но не очень много в поддержку стандарта (C89). Например:
- Наличие длины в буфере может предотвратить переполнение буфера.
- Стиль Java
arr.length
понятен и избавляет программиста от необходимости поддерживать многоint
s в стеке при работе с несколькими массивами. - Параметры функции становятся более убедительными.
Но, пожалуй, самая мотивирующая причина, на мой взгляд, заключается в том, что обычно не сохраняется место без сохранения длины. Рискну сказать, что в большинстве случаев использование массивов связано с динамическим распределением. Правда, могут быть случаи, когда люди используют массив, выделенный в стеке, но это всего лишь один вызов функции * - стек может обрабатывать дополнительно 4 или 8 байтов.
Поскольку диспетчер кучи должен отслеживать размер свободного блока, используемого динамически размещаемым массивом, в любом случае, почему бы не сделать эту информацию пригодной для использования (и добавить дополнительное правило, проверенное во время компиляции), что нельзя манипулировать длиной явно, если не будет люблю стрелять себе в ногу).
Единственное , что я могу думать на другой стороне, что ни трекинга длина не может быть сделано компиляторы проще, но не , что гораздо проще.
* Технически, можно написать некую рекурсивную функцию с массивом с автоматическим хранением, и в этом (очень сложном) случае сохранение длины может действительно привести к эффективному использованию пространства.
malloc()
редактируемой области переносимым способом?» Это то, что заставляет меня удивляться несколько раз.Ответы:
Массивы C отслеживают их длину, так как длина массива является статическим свойством:
Обычно вы не можете запросить эту длину, но вам это не нужно, потому что она все равно статическая - просто объявите макрос
XS_LENGTH
для длины, и все готово.Более важная проблема заключается в том, что массивы C неявно разлагаются на указатели, например, когда передаются функции. Это имеет некоторый смысл и допускает некоторые приятные трюки низкого уровня, но при этом теряется информация о длине массива. Поэтому лучшим вопросом было бы, почему C был разработан с этим неявным ухудшением указателей.
Другое дело, что указатели не нуждаются в хранении, кроме самого адреса памяти. C позволяет нам приводить целые числа к указателям, указатели на другие указатели и обрабатывать указатели, как если бы они были массивами. Делая это, C не настолько безумен, чтобы создать некоторую длину массива, но, похоже, верит в девиз Spiderman: с большой силой, мы надеемся, программист выполнит большую ответственность за отслеживание длин и переполнений.
источник
sizeof(xs)
гдеxs
находится массив, будет чем-то другим в другой области видимости, является явно ложным, потому что структура C не позволяет массивам покидать их область видимости. Еслиsizeof(xs)
гдеxs
массив отличается от того,sizeof(xs)
гдеxs
указатель, это неудивительно, потому что вы сравниваете яблоки с апельсинами .Многое из этого было связано с компьютерами, доступными в то время. Мало того, что скомпилированная программа должна была работать на компьютере с ограниченными ресурсами, но, что еще более важно, сам компилятор должен был работать на этих машинах. В то время, когда Томпсон разработал C, он использовал PDP-7 с 8 КБ ОЗУ. Сложные языковые функции, которые не имели непосредственного аналога в реальном машинном коде, просто не были включены в язык.
Внимательное чтение истории C дает более глубокое понимание вышесказанного, но это не было полностью результатом ограничений машины, которые у них были:
С-массивы по своей природе более мощные. Добавление к ним границ ограничивает то, для чего программист может их использовать. Такие ограничения могут быть полезны для программистов, но обязательно также являются ограничивающими.
источник
to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator
- ну, очень много для этого :-)Назад в тот день, когда был создан C, и дополнительные 4 байта пространства для каждой строки, независимо от того, насколько коротким был бы пустая трата!
Есть еще одна проблема - помните, что C не является объектно-ориентированным, поэтому, если вы делаете префикс длины для всех строк, он должен быть определен как внутренний тип компилятора, а не как a
char*
. Если бы это был специальный тип, то вы не смогли бы сравнить строку с константной строкой, то есть:потребовалось бы иметь специальные детали компилятора, чтобы либо преобразовать эту статическую строку в строку, либо иметь другие строковые функции для учета префикса длины.
В конечном счете, я думаю, что они просто не выбрали путь префикса длины в отличие от, скажем, Паскаля.
источник
for
цикл уже настроен на соблюдение границ.В C любое смежное подмножество массива также является массивом и может использоваться как таковое. Это относится как к операциям чтения, так и записи. Это свойство не будет храниться, если размер был сохранен явно.
источник
&[T]
типов, например.Самая большая проблема с массивами, помеченными их длиной, это не столько пространство, которое требуется для хранения этой длины, ни вопрос о том, как ее хранить (использование одного дополнительного байта для коротких массивов обычно не будет нежелательным, равно как и использование четырех). дополнительные байты для длинных массивов, но использование четырех байтов даже для коротких массивов может быть). Гораздо большая проблема в том, что данный код выглядит так:
единственный способ, которым код мог бы принять первый вызов,
ClearTwoElements
но отклонить второй, - это чтобыClearTwoElements
метод получил информацию, достаточную для того, чтобы знать, что в каждом случае он получал ссылку на часть массиваfoo
в дополнение к знанию какой части. Это обычно удваивает стоимость передачи параметров указателя. Кроме того, если каждому массиву предшествует указатель на адрес сразу после конца (наиболее эффективный формат для проверки), оптимизированный код для негоClearTwoElements
, вероятно, станет примерно таким:Обратите внимание, что вызывающий метод, в общем, вполне законно может передать указатель на начало массива или последний элемент метода; только если метод попытается получить доступ к элементам, которые выходят за пределы переданного массива, такие указатели вызовут какие-либо проблемы. Следовательно, вызываемый метод должен был бы сначала убедиться, что массив был достаточно большим, чтобы арифметика указателя для проверки его аргументов сама по себе не выходила за пределы, а затем выполнить некоторые вычисления указателя для проверки аргументов. Время, потраченное на такую проверку, вероятно, превысит затраты, потраченные на выполнение любой реальной работы. Кроме того, метод мог бы быть более эффективным, если бы он был написан и вызван:
Идея типа, который объединяет что-то для идентификации объекта с чем-то для идентификации его части, является хорошей. Однако указатель в стиле C быстрее, если нет необходимости выполнять проверку.
источник
[]
Синтаксис может все еще существовать для указателей, но он будет отличаться от этих гипотетических «реальных» массивов, и описанная вами проблема, вероятно, не будет существовать.Одно из фундаментальных отличий между C и большинством других языков 3-го поколения и всеми более свежими языками, которые мне известны, заключается в том, что C не был разработан, чтобы сделать жизнь программиста проще или безопаснее. Он был разработан с расчетом на то, что программист знает, что делает, и хочет делать именно и только это. Он не делает ничего «за кадром», поэтому вы не получите никаких сюрпризов. Даже оптимизация на уровне компилятора не обязательна (если вы не используете компилятор Microsoft).
Если программист хочет написать проверку границ в своем коде, C делает это достаточно просто, но программист должен решить заплатить соответствующую цену с точки зрения пространства, сложности и производительности. Несмотря на то, что я не использовал его в гневе в течение многих лет, я все еще использую его при обучении программированию, чтобы понять концепцию принятия решений на основе ограничений. По сути, это означает, что вы можете делать все, что захотите, но каждое принятое вами решение имеет цену, о которой вы должны знать. Это становится еще более важным, когда вы начинаете говорить другим, что вы хотите, чтобы их программы делали.
источник
int f[5];
бы не создавалосьf
как массив из пяти элементов; вместо этого это было эквивалентноint CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;
. Предыдущее объявление может быть обработано без необходимости «понимать» время массива компилятором; он просто должен был вывести директиву ассемблера для выделения пространства и затем мог забыть, чтоf
когда-либо имел какое-либо отношение к массиву. Непоследовательное поведение типов массивов проистекает из этого.Краткий ответ:
Поскольку C является языком программирования низкого уровня , он ожидает, что вы сами позаботитесь об этих проблемах, но это добавляет большую гибкость в том, как именно вы его реализуете.
C имеет концепцию во время компиляции массива, который инициализируется с длиной, но во время выполнения все это просто сохраняется как один указатель на начало данных. Если вы хотите передать длину массива функции вместе с массивом, вы делаете это самостоятельно:
Или вы можете использовать структуру с указателем и длиной, или любое другое решение.
Язык более высокого уровня сделает это за вас как часть типа массива. В Си вы несете ответственность за выполнение этого самостоятельно, а также за гибкость выбора того, как это сделать. И если весь код, который вы пишете, уже знает длину массива, вам вообще не нужно передавать длину как переменную.
Очевидный недостаток заключается в том, что без проверки внутренних границ массивов, передаваемых в виде указателей, вы можете создавать опасный код, но это природа языков низкого уровня / систем и компромисса, который они дают.
источник
Проблема дополнительного хранилища - это проблема, но, на мой взгляд, незначительная. В конце концов, в большинстве случаев вам все равно придется отслеживать длину, хотя amon подчеркнул, что ее часто можно отслеживать статически.
Большая проблема заключается в том, где хранить длину и как долго ее делать. Нет одного места, которое работает во всех ситуациях. Вы можете сказать, просто сохранить длину в памяти непосредственно перед данными. Что если массив не указывает на память, а что-то вроде буфера UART?
Отсутствие длины позволяет программисту создавать свои собственные абстракции для соответствующей ситуации, и есть множество готовых библиотек, доступных для случая общего назначения. Реальный вопрос в том, почему эти абстракции не используются в приложениях, чувствительных к безопасности?
источник
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?
Не могли бы вы объяснить это немного больше? Кроме того, это может случиться слишком часто или это просто редкий случай?T[]
, не был бы эквивалентен,T*
а скорее передавал бы указатель и размер функции. Массивы фиксированного размера могут распадаться на такой срез массива, вместо того, чтобы распадаться на указатели, как они делают в C. Основное преимущество этого подхода не в том, что он сам по себе безопасен, но это соглашение, на котором все, включая стандартную библиотеку, может строить.Из развития языка C :
В этом отрывке объясняется, почему выражения массивов в большинстве случаев распадаются на указатели, но те же соображения применимы к тому, почему длина массива не сохраняется в самом массиве; если вы хотите, чтобы между определением типа и его представлением в памяти было однозначное соответствие (как это сделал Ричи), тогда нет подходящего места для хранения этих метаданных.
Кроме того, подумайте о многомерных массивах; где бы вы хранили метаданные длины для каждого измерения, чтобы вы могли пройти через массив с чем-то вроде
источник
Вопрос предполагает, что в C. есть массивы. Вещи, которые называются массивами, являются просто синтаксическим сахаром для операций с непрерывными последовательностями данных и арифметикой указателей.
Следующий код копирует некоторые данные из src в dst кусками типа int, не зная, что это на самом деле символьная строка.
Почему C настолько упрощен, что не имеет правильных массивов? Я не знаю правильного ответа на этот новый вопрос. Но некоторые люди часто говорят, что C - просто (несколько) более читаемый и портативный ассемблер.
источник
struct Foo { int arr[10]; }
.arr
это массив, а не указатель.