Стандарт C11 гласит, что массивы как размера, так и переменной длины «должны иметь значение больше нуля». Каково оправдание для того, чтобы не допустить длину 0?
Особенно для массивов переменной длины имеет смысл иметь размер ноль время от времени. Это также полезно для статических массивов, когда их размер взят из макроса или опции конфигурации сборки.
Интересно, что GCC (и clang) предоставляют расширения, которые допускают массивы нулевой длины. Java также допускает массивы нулевой длины.
struct { int p[1],q[1]; } foo; int *pp = p+1;
,pp
будет законным указателем, но*pp
не будет иметь уникальный адрес. Почему та же логика не может выполняться с массивом нулевой длины? Говорят , что даноint q[0];
в структуре ,q
будет ссылаться на адрес которого действительность будет , как , что вp+1
приведенном выше примере.Ответы:
Я хотел бы поспорить, что массивы C - это всего лишь указатели на начало выделенного фрагмента памяти. Наличие размера 0 будет означать, что у вас есть указатель на ... ничего? Вы не можете иметь ничего, поэтому пришлось бы выбирать какую-то произвольную вещь. Вы не можете использовать
null
, потому что тогда ваши массивы 0 будут выглядеть как нулевые указатели. И в этот момент каждая другая реализация выберет различное произвольное поведение, что приведет к хаосу.источник
sizeof
0, и как это вызовет проблемы. Все это можно объяснить, используя правильные понятия и терминологию без потери краткости или ясности. Смешивание массивов и указателей только рискует распространить массивы = неправильное представление указателей (что более важно в других контекстах) без пользы.Давайте посмотрим, как массив обычно располагается в памяти:
Обратите внимание, что не существует отдельного объекта с именем,
arr
который хранит адрес первого элемента; когда в выражении появляется массив, C вычисляет адрес первого элемента по мере необходимости.Итак, давайте думать об этом: массив 0-элемент не будет иметь не хранения отведенные для него, а это означает , что нет ничего , чтобы вычислить адрес массива из (иначе говоря, нет никакого отображения объекта для идентификатора). Это все равно что сказать: «Я хочу создать
int
переменную, которая не занимает памяти». Это бессмысленная операция.редактировать
Java-массивы полностью отличаются от массивов C и C ++; это не примитивный тип, а ссылочный тип, полученный из
Object
.Редактировать 2
Точка, затронутая в комментариях ниже - ограничение «больше 0» применяется только к массивам, размер которых указан через константное выражение ;
VLA может иметь длину 0.Объявление VLA с 0-значным непостоянным выражением не является нарушением ограничения, но оно вызывает неопределенное поведение.Понятно, что VLA - это разные животные от обычных массивов
, и их реализация может учитывать размер 0. Они не могут быть объявленыstatic
или находятся в области видимости файла, потому что размер таких объектов должен быть известен до запуска программы.Также ничего не стоит, что начиная с C11 реализации не обязаны поддерживать VLA.
источник
T a[0]
какT *a
, но тогда почему бы просто не использоватьT *a
?struct
Хак. Я никогда не использовал это лично; никогда не работал над проблемой, которая нуждалась вstruct
типе переменного размера .Вы обычно хотели бы, чтобы ваш массив нулевого (фактически переменного) размера знал свой размер во время выполнения. Затем упакуйте это в a
struct
и используйте элементы гибкого массива , например, например:Очевидно, что член гибкого массива должен быть последним в своем,
struct
и вам нужно что-то иметь до этого. Часто это может быть связано с фактической занятой во время выполнения длиной этого гибкого элемента массива.Конечно, вы бы выделить:
AFAIK, это было уже возможно в C99, и это очень полезно.
Кстати, гибких элементов массива не существует в C ++ (потому что было бы трудно определить, когда и как они должны быть построены и уничтожены). Смотрите, однако, будущее std :: dynarray
источник
Если выражение
type name[count]
записано в какой-то функции, то вы указываете компилятору C выделитьsizeof(type)*count
байты кадра стека и вычислить адрес первого элемента в массиве.Если выражение
type name[count]
написано вне всех функций и структурирует определения, то вы указываете компилятору C выделитьsizeof(type)*count
байты сегмента данных и вычислить адрес первого элемента в массиве.name
на самом деле это константный объект, который хранит адрес первого элемента в массиве, и каждый объект, который хранит адрес какой-либо памяти, называется указателем, поэтому эту причину вы рассматриваетеname
как указатель, а не как массив. Обратите внимание, что массивы в C могут быть доступны только через указатели.Если
count
это константное выражение, которое оценивается как ноль, то вы указываете компилятору C выделить нулевые байты либо в кадре стека, либо в сегменте данных и вернуть адрес первого элемента в массиве, но проблема заключается в том, что первый элемент массива нулевой длины не существует, и вы не можете вычислить адрес чего-то, что не существует.Это рационально, что элемента нет.
count+1
не существует вcount
массиве с длинной длиной, так что это причина, по которой компилятор C запрещает определять массив нулевой длины как переменную внутри и снаружи функции, потому что в чем тогда содержимоеname
? Какой адресname
хранит именно?Если
p
указатель, то выражениеp[n]
эквивалентно*(p + n)
Когда звездочка * в правом выражении является операцией разыменования указателя, что означает доступ к памяти, на которую указывает указатель,
p + n
или доступ к памяти, адрес которой хранитсяp + n
, гдеp + n
указатель выражения, он берет адресp
и добавляет к этому адресу число,n
умноженное на размер типа указателяp
.Можно ли добавить адрес и номер?
Да, это возможно, потому что адрес представляет собой целое число без знака, обычно представляемое в шестнадцатеричном формате.
источник
N
имеетN+1
ассоциированные адреса, первыйN
из которых идентифицирует уникальные байты, а последнийN
из которых указывает только один из этих байтов. Такое определение будет прекрасно работать даже в вырожденном случае, гдеN
0.Если вы хотите указатель на адрес памяти, объявите его. Массив фактически указывает на кусок памяти, который вы зарезервировали. Массивы распадаются на указатели при передаче в функции, но если память, на которую они указывают, находится в куче, нет проблем. Нет причин объявлять массив нулевого размера.
источник
Со времен первоначального C89, когда в стандарте C указывалось, что что-то имеет неопределенное поведение, это означало: «Делайте все, что сделает реализацию на конкретной целевой платформе наиболее подходящей для ее предполагаемого назначения». Авторы Стандарта не хотели пытаться угадать, какое поведение может быть наиболее подходящим для какой-либо конкретной цели. Существующие реализации C89 с расширениями VLA могли иметь разные, но логичные, поведения при заданном размере нуля (например, некоторые могли бы трактовать массив как выражение адреса, приводящее к NULL, в то время как другие рассматривали его как выражение адреса, которое могло бы равняться адресу другая произвольная переменная, но в нее можно было бы добавить ноль без перехвата). Если какой-либо код полагается на такое различное поведение, авторы Стандарта не
Вместо того, чтобы пытаться угадать, что могут делать реализации, или предлагать, чтобы какое-либо поведение считалось превосходящим любое другое, авторы Стандарта просто позволяли разработчикам использовать суждение при рассмотрении этого случая так, как им было удобно. Реализации, которые используют malloc () за кулисами, могут обрабатывать адрес массива как NULL (если нулевой размер malloc приводит к нулю), те, которые используют вычисления стекового адреса, могут давать указатель, который совпадает с адресом какой-либо другой переменной, и некоторые другие реализации могут делать другие вещи. Я не думаю, что они ожидали, что авторы компиляторов сделают все возможное, чтобы заставить случай с нулевым размером вести себя преднамеренно бесполезно.
источник