"int * nums = {5, 2, 1, 4}"로 인해 세그멘테이션 오류가 발생합니다.


81
int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);

세그 폴트를 일으키는 반면

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를 deref하려고 시도하여 segfault를 발생시킵니다.

내가 틀렸다면 정정하십시오. 그리고 내가 맞다면 자세히 설명해주세요. 왜 배열 이니셜 라이저가 작동하는 방식을 이해하지 못하기 때문입니다.



3
모든 경고가 활성화 된 상태에서 컴파일하면 컴파일러가 어떤 일이 발생하는지 알려줍니다.
Jabberwocky

1
@GSerg 그것은 중복 근처 어디에도 없습니다. 이 질문에는 배열 포인터가 없습니다. 해당 게시물의 일부 답변은 여기와 유사합니다.
Lundin

2
@Lundin 저는 30 % 확신을 가지고 투표를하지 않고 링크 만 게시했습니다.
GSerg

3
-pedantic-errors플래그로 GCC를 실행하는 습관을 갖고 진단을 관찰하십시오. int *nums = {5, 2, 1, 4};C.
AnT

답변:


113

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 초기화 는 다음과 같이 말합니다.

2 이니셜 라이저는 초기화되는 엔티티에 포함되지 않은 객체에 대한 값을 제공하려고 시도해서는 안됩니다.

/-/

11 스칼라의 이니셜 라이저는 선택적으로 중괄호로 묶인 단일 표현식이어야합니다. 객체의 초기 값은 변환 후 표현식의 값입니다. 단순 할당과 동일한 유형 제약 및 변환이 적용되며 스칼라 유형을 선언 된 유형의 정규화되지 않은 버전으로 취합니다.

"스칼라 유형"은 배열, 구조체 또는 공용체 유형 ( "집계 유형"이라고 함)이 아닌 단일 변수를 나타내는 표준 용어입니다.

따라서 일반 영어에서 표준은 "변수를 초기화 할 때 가능하기 때문에 이니셜 라이저 표현식 주위에 추가 중괄호를 넣어도됩니다."라고 말합니다.


11
에 묶인 단일 값으로 스칼라 객체를 초기화하는 기능에 대해 "어리석은"것은 없습니다 {}. 반대로 C 언어의 가장 중요하고 편리한 관용구 중 하나 { 0 }인 범용 제로 이니셜 라이저를 용이하게 합니다. C의 모든 것은를 통해 0으로 초기화 될 수 있습니다 = { 0 }. 이것은 유형 독립적 코드를 작성하는 데 매우 중요합니다.
AnT

3
@AnT "유니버설 제로 이니셜 라이저"와 같은 것은 없습니다. 집계의 경우 {0}첫 번째 개체를 0으로 초기화하고 나머지 개체를 마치 정적 저장 기간이있는 것처럼 초기화하는 것을 의미합니다. {1}모든 개체를 1로 초기화하지 않기 때문에 일부 "유니버설 이니셜 라이저"의 의도적 인 언어 디자인이 아니라 우연의 일치라고 말할 수 있습니다.
Lundin

3
@Lundin C11 6.5.16.1/1은 다룹니다 p = 5;(포인터에 정수를 할당하기 위해 나열된 케이스 중 어느 것도 충족되지 않음). 6.7.9 / 11은 할당에 대한 제약이 초기화에도 사용된다는 것을 말합니다.
MM

4
@Lundin : 네, 있습니다. 어떤 메커니즘이 객체의 어떤 부분을 초기화하는지는 완전히 관련이 없습니다. {}스칼라의 초기화가 그 목적을 구체적으로 허용 하는지 여부는 또한 완전히 관련이 없습니다 . 중요한 것은 = { 0 }이니셜 라이저가 전체 객체0으로 초기화 한다는 것입니다 . 이것이 바로 C 언어의 고전적이고 가장 우아한 관용구 중 하나가 된 이유입니다.
AnT

2
@Lundin : 당신의 발언 {1}이 주제와 어떤 관련 이 있는지도 저에게 완전히 불분명합니다 . 집계의 모든 구성원에 대한 다중 초기화 프로그램으로 {0}해석한다고 주장하는 사람은 아무도 없습니다 0.
AnT

28

시나리오 1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault

이것은 왜 세그 폴트입니까?

당신은 numsint에 대한 포인터로 선언 했습니다 . 이것은 메모리 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.

포인터 변수에 여러 값을 할당하는 경우에, 프로그램 세그먼테이션 폴트 (segfault) 당신은에 액세스 할 때 nums[0],에 저장되어 당신이 무엇을 deferencing하는 수단 주소 (5) 문자를. nums이 경우 포인터 에 유효한 메모리를 할당하지 않았습니다 .

int 변수에 여러 값을 할당하는 경우에는 segfault가 없다는 점에 주목할 가치가 있습니다 (여기서는 잘못된 포인터를 역 참조하지 않습니다).


시나리오 2

int nums[] = {5, 2, 1, 4};

이것은 합법적으로 스택에 4 개의 int 배열을 할당하고 있기 때문에 segfault가 아닙니다.


시나리오 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; 

2
+1 좋은 조언이지만 많은 플랫폼의 마법 주소를 감안할 때 "절대"는 정말 너무 강력합니다. (그 고정 주소에 대해 상수 테이블을 사용하는 것은 현존하는 변수를 가리키는 것이 아니므로 명시된대로 규칙을 위반합니다.) 드라이버 개발과 같은 낮은 수준의 항목은 이러한 종류를 꽤 자주 다룹니다.
The Nate

3
"유효합니다"-초과 이니셜 라이저를 무시하는 것은 GCC 확장입니다. 허용되지 않는 표준 C에서
MM

1
@TheNate-네 맞습니다. 귀하의 의견을 바탕으로 편집했습니다. 감사합니다.
artm

@MM-지적 해 주셔서 감사합니다. 나는 그것을 제거하기 위해 편집했습니다.
artm

25

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있습니까?
supercat

@supercat 예, nums가 자동이면 자동이고 nums가 정적이면 정적입니다
MM

@MM : nums함수 내에서 선언 된 정적 변수 인 경우에도 적용됩니까? 아니면 컴파일러가 정적 변수에 할당 되었더라도 배열의 수명을 둘러싸는 블록의 수명으로 제한 할 자격이 있습니까?
supercat

@supercat yes (첫 번째 비트). 두 번째 옵션은 함수가 두 번째로 호출 될 때 UB를 의미합니다 (정적 변수는 첫 번째 호출에서만 초기화되므로)
MM

12
int *nums = {5, 2, 1, 4};

nums유형의 포인터입니다 int. 따라서이 지점을 유효한 메모리 위치로 지정해야합니다. num[0]임의의 메모리 위치를 역 참조하려고하므로 분할 오류가 발생합니다.

예, 포인터는 값 5를 보유하고 있으며 시스템에서 정의되지 않은 동작을 역 참조하려고합니다. ( 5은 시스템에서 유효한 메모리 위치가 아닌 것 같습니다. )

이므로

int nums[] = {1,2,3,4};

nums유형의 배열이고 int초기화 중에 전달 된 요소 수에 따라 메모리가 할당 되는 유효한 선언 입니다.


1
"네, 포인터가 값 5를 유지하고 있으며 정의되지 않은 동작을 역 참조하려고합니다." 전혀, 완벽하고 잘 정의 된 동작입니다. 그러나 OP가 사용하는 시스템에서는 유효한 메모리 주소가 아니므로 충돌이 발생합니다.
Lundin

@Lundin 동의합니다. 그러나 나는 OP가 5가 유효한 메모리 위치라는 것을 결코 몰랐기 때문에 그 라인에 대해 이야기했습니다. 편집이 도움이
되기를 바랍니다

이럴까요? int *nums = (int[]){5, 2, 1, 4};
Islam Azab

10

할당함으로써 {5, 2, 1, 4}

int *nums = {5, 2, 1, 4};

당신은 5를 할당하고 있습니다 nums(int에서 int에 대한 포인터로 암시 적 유형 변환 후). 그것을 연기하면에서 메모리 위치에 대한 액세스 호출이 생성됩니다 0x5. 귀하의 프로그램에서 액세스가 허용되지 않을 수 있습니다.

시험

printf("%p", (void *)nums);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.