"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);