"int * nums = {5, 2, 1, 4}" вызывает ошибку сегментации
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.
Если я ошибаюсь, пожалуйста, поправьте меня. И если я прав, пожалуйста, уточните, потому что я не понимаю, почему инициализаторы массивов работают так, как они делают.
Ответы
Ответ 1
В C существует (глупое) правило, говорящее, что любая простая переменная может быть инициализирована с помощью списка инициализаторов, заключенных в скобки, так же, как если бы это был массив.
Например, вы можете написать int x = {0};
, что полностью эквивалентно int x = 0;
.
Итак, когда вы пишете int *nums = {5, 2, 1, 4};
, вы фактически предоставляете список инициализаторов одной переменной-указателю. Тем не менее, это всего лишь одна переменная, поэтому ей будет присвоено первое значение 5, остальная часть списка будет проигнорирована (на самом деле я не думаю, что код с избыточными инициализаторами должен даже компилироваться со строгим компилятором) - это не вообще записываться в память. Код эквивалентен int *nums = 5;
. Это означает, что nums
должен указывать на адрес 5
.
В этот момент вы уже должны получить два предупреждения/ошибки компилятора:
- Назначение целого числа указателю без трансляции.
- Избыточные элементы в списке инициализаторов.
И тогда, конечно, код сработает и сгорит, так как 5
, скорее всего, не является допустимым адресом, вам разрешено разыгрывать nums[0]
.
В качестве побочного примечания вы должны printf
указать указатели с помощью спецификатора %p
или иначе вы вызываете поведение undefined.
Я не совсем уверен, что вы здесь делаете, но если вы хотите установить указатель на массив в массиве, вы должны сделать:
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 Инициализация говорит (акцент мой):
2 Ни один инициализатор не попытается предоставить значение для объекта, а не содержащихся в инициализированном объекте.
/-/
11 Инициализатор для скаляра должен быть единственным выражением, возможно, заключен в фигурные скобки. Начальное значение объекта заключается в том, что выражения (после преобразования); те же ограничения типа и применяются преобразования для простого присваивания, принимая вид скаляр, чтобы быть неквалифицированной версией его объявленного типа.
"Скалярный тип" - это стандартный термин, относящийся к одиночным переменным, которые не относятся к типу массива, структуры или объединения (называются "агрегатным типом" ).
Итак, на простом английском языке в стандарте говорится: "Когда вы инициализируете переменную, не стесняйтесь бросать некоторые дополнительные фигурные скобки вокруг выражения инициализатора, просто потому, что вы можете".
Ответ 2
СЦЕНАРИЙ 1
int *nums = {5, 2, 1, 4}; // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]); // segfault
Почему это одно segfault?
Вы объявили nums
как указатель на int - то есть nums
должен содержать адрес одного целого числа в памяти.
Затем вы попытались инициализировать nums
массивом значений multiple. Поэтому, не копаясь в деталях, это концептуально неверно. Не имеет смысла назначать несколько значений переменной, которая должна содержать одно значение. В этом отношении вы увидите точно такой же эффект, если вы это сделаете:
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
.
В случае назначения нескольких значений переменной-указателю программа segfaults при доступе к nums[0]
, что означает, что вы отменяете все, что хранится в адресе 5 буквально. В этом случае вы не выделяли какую-либо допустимую память для указателя nums
.
Стоит отметить, что не существует segfault для случая назначения нескольких значений переменной int (вы не разыскиваете какой-либо недопустимый указатель здесь).
СКЭНАРИО 2
int nums[] = {5, 2, 1, 4};
Это не segfault, потому что вы юридически выделяете массив из 4 ints в стеке.
СЦЕНАРИЙ 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;
Ответ 3
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
для определения того, как долго будет массив.
Ответ 4
int *nums = {5, 2, 1, 4};
nums
является указателем типа int
. Таким образом, вы должны сделать это в некоторой допустимой ячейке памяти. num[0]
вы пытаетесь разыменовать случайную ячейку памяти и, следовательно, ошибку сегментации.
Да, указатель удерживает значение 5, и вы пытаетесь разыменовать поведение undefined в вашей системе. (Похоже, что 5
не является допустимой ячейкой памяти в вашей системе)
В то время как
int nums[] = {1,2,3,4};
является допустимым объявлением, в котором вы говорите, что nums
- это массив типа int
, а память распределяется на основе количества элементов, переданных во время инициализации.
Ответ 5
Назначая {5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
вы назначаете 5 в nums
(после неявного typecast от int до указателя на int). Dereferring делает вызов доступа к ячейке памяти в 0x5
. Это может быть запрещено для вашей программы.
Try
printf("%p", (void *)nums);