int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
вызывает segfault, тогда как
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
нет. В настоящее время:
int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
печатает 5.
Основываясь на этом, я предположил, что обозначение инициализации массива, {}, слепо загружает эти данные в любую переменную слева. Когда это int [], массив заполняется по желанию. Когда это int *, указатель заполняется на 5, а ячейки памяти после того, где хранится указатель, заполняются на 2, 1 и 4. Таким образом, nums [0] пытается разыграть 5, вызывая segfault.
Если я ошибаюсь, поправьте меня. И если я прав, пожалуйста, уточните, потому что я не понимаю, почему инициализаторы массива работают так, как они.
-pedantic-errors
флагом и смотреть диагностику.int *nums = {5, 2, 1, 4};
недействителен C.Ответы:
В C есть (глупое) правило, согласно которому любая простая переменная может быть инициализирована с помощью списка инициализаторов, заключенного в фигурные скобки, как если бы это был массив.
Например, вы можете написать
int x = {0};
, что полностью эквивалентноint x = 0;
.Поэтому, когда вы пишете,
int *nums = {5, 2, 1, 4};
вы фактически даете список инициализаторов для одной переменной-указателя. Однако это всего лишь одна переменная, поэтому ей будет присвоено только первое значение 5, остальная часть списка игнорируется (на самом деле я не думаю, что код с избыточными инициализаторами должен даже компилироваться с помощью строгого компилятора) - он не вообще записываются в память. Код эквивалентенint *nums = 5;
. Это означает, что онnums
должен указывать на адрес5
.На этом этапе вы уже должны были получить два предупреждения / ошибки компилятора:
И тогда, конечно, код выйдет из строя и сгорит, поскольку
5
, скорее всего, это неверный адрес, с которым вам разрешено разыменоватьnums[0]
.В качестве побочного примечания вы должны
printf
указывать адреса с помощью%p
спецификатора, иначе вы вызовете неопределенное поведение.Я не совсем уверен, что вы здесь пытаетесь сделать, но если вы хотите установить указатель, указывающий на массив, вы должны сделать:
int nums[] = {5, 2, 1, 4}; int* ptr = nums; // or equivalent: int* ptr = (int[]){5, 2, 1, 4};
Или, если вы хотите создать массив указателей:
int* ptr[] = { /* whatever makes sense here */ };
РЕДАКТИРОВАТЬ
После некоторого исследования я могу сказать, что «список инициализаторов лишних элементов» действительно недействителен C - это расширение GCC .
Стандартная инициализация 6.7.9 говорит (выделено мной):
«Скалярный тип» - это стандартный термин, относящийся к одиночным переменным, которые не относятся к типу массива, структуры или объединения (они называются «агрегатным типом»).
Таким образом, на простом английском языке стандарт гласит: «когда вы инициализируете переменную, не стесняйтесь вставлять дополнительные фигурные скобки вокруг выражения инициализатора просто потому, что вы можете».
источник
{}
. Напротив, он упрощает использование одной из самых важных и удобных идиом языка C -{ 0 }
универсального инициализатора нуля. Все в C может быть инициализировано нулями= { 0 }
. Это очень важно для написания кода, независимого от типа.{0}
просто означает инициализировать первый объект до нуля и инициализировать остальные объекты, как если бы они имели статическую продолжительность хранения. Я бы сказал, что это скорее совпадение, чем намеренный языковой дизайн какого-то «универсального инициализатора», поскольку{1}
не инициализирует все объекты значением 1.p = 5;
(ни один из перечисленных случаев не выполняется для назначения целого числа указателю); а 6.7.9 / 11 говорит, что ограничения для назначения также используются для инициализации.{}
разрешена ли инициализация скаляров специально для этой цели. Единственное, что имеет значение, это то, что= { 0 }
инициализатор гарантированно инициализирует нулем весь объект , что и сделало его классической и одной из самых элегантных идиом языка C.{1}
имеет ваше замечание к теме. Никто никогда не утверждал, что{0}
интерпретирует это0
как мультиинициализатор для каждого члена агрегата.СЦЕНАРИЙ 1
Почему это один segfault?
Вы объявили
nums
как указатель на int -nums
он должен хранить в памяти адрес одного целого числа.Затем вы попытались инициализировать
nums
массив из нескольких значений. Поэтому, если не вдаваться в подробности, это концептуально неверно - бессмысленно назначать несколько значений переменной, которая должна содержать одно значение. В этом отношении вы увидите точно такой же эффект, если сделаете это:int nums = {5, 2, 1, 4}; // <-- assign multiple values to an int variable printf("%d\n", nums); // also print 5
В любом случае (назначьте несколько значений указателю или переменной типа int), тогда произойдет то, что переменная получит первое значение, которое
5
, а остальные значения игнорируются. Этот код соответствует требованиям, но вы получите предупреждения для каждого дополнительного значения, которого не должно быть в назначении:warning: excess elements in scalar initializer
.В случае присвоения нескольких значений переменной-указателю, программа перестает работать при доступе
nums[0]
, что означает, что вы буквально отсылаете все, что хранится по адресу 5 . В этом случае вы не выделили допустимую память для указателяnums
.Стоит отметить, что для случая присвоения нескольких значений переменной int нет segfault (здесь вы не разыменовываете какой-либо недопустимый указатель).
СЦЕНАРИЙ 2
int nums[] = {5, 2, 1, 4};
Это не segfault, потому что вы на законных основаниях размещаете в стеке массив из 4 целых чисел.
СЦЕНАРИЙ 3
int *nums = {5, 2, 1, 4}; printf("%d\n", nums); // print 5
Это не segfault, как ожидалось, потому что вы печатаете значение самого указателя - НЕ то, что он разыменовывает (что является недопустимым доступом к памяти).
Другие
Он почти всегда обречен на segfault всякий раз, когда вы
жестко кодируете значение указателя,подобного этому (потому что это задача операционной системы - определить, какой процесс может получить доступ к какой области памяти).int *nums = 5; // <-- segfault
Итак, практическое правило - всегда инициализировать указатель на адрес некоторой выделенной переменной, например:
int a; int *nums = &a;
или же,
int a[] = {5, 2, 1, 4}; int *nums = a;
источник
int *nums = {5, 2, 1, 4};
неправильно сформированный код. Существует расширение GCC, которое обрабатывает этот код так же, как:int *nums = (int *)5;
попытка сформировать указатель на адрес памяти 5. (Мне это не кажется полезным расширением, но я думаю, что база разработчиков этого хочет).
Чтобы избежать такого поведения (или, по крайней мере, получить предупреждение), вы можете скомпилировать в стандартном режиме, например
-std=c11 -pedantic
.Альтернативная форма действительного кода:
int *nums = (int[]){5, 2, 1, 4};
который указывает на изменяемый литерал той же продолжительности хранения, что и
nums
. Однако этаint nums[]
версия обычно лучше, поскольку использует меньше памяти, и вы можете использовать ееsizeof
для определения длины массива.источник
nums
?nums
это статическая переменная, объявленная в функции, или компилятор будет иметь право ограничить время жизни массива временем жизни включающего блока, даже если он был назначен статической переменной?int *nums = {5, 2, 1, 4};
nums
указатель типаint
. Таким образом, вы должны указать на некоторую допустимую область памяти.num[0]
вы пытаетесь разыменовать какую-то случайную ячейку памяти и, следовательно, ошибку сегментации.Да, указатель имеет значение 5, и вы пытаетесь разыменовать его, что является неопределенным поведением в вашей системе. (Выглядит как
5
это недопустимая ячейка памяти в вашей системе)В то время как
int nums[] = {1,2,3,4};
- допустимое объявление, в котором вы говорите,
nums
что это массив типа,int
а память выделяется на основе количества элементов, переданных во время инициализации.источник
int *nums = (int[]){5, 2, 1, 4};
Назначив
{5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
вы назначаете 5
nums
(после неявного преобразования типа из int в указатель на int). Отмена ссылки делает вызов доступа к ячейке памяти по адресу0x5
. Это может быть запрещено вашей программе.Пытаться
printf("%p", (void *)nums);
источник