[c] C에서 배열 초기화에 대한 혼란
C 언어에서 다음과 같이 배열을 초기화하면 :
int a[5] = {1,2};
그러면 명시 적으로 초기화되지 않은 배열의 모든 요소는 암시 적으로 0으로 초기화됩니다.
그러나 다음과 같이 배열을 초기화하면 :
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
산출:
1 0 1 0 0
이해가 안되는데 왜 대신 a[0]
인쇄 합니까? 정의되지 않은 동작입니까?1
0
참고 : 이 질문은 인터뷰에서 요청되었습니다.
답변
요약 : int a[5]={a[2]=1};
최소한 C99에서는 의 동작 이 잘 정의 되어 있지 않다고 생각합니다 .
재미있는 부분은 나에게 이해되는 유일한 부분은 당신이 묻는 부분 이라는 것입니다. 할당 연산자가 할당 된 값을 반환 a[0]
하기 1
때문에 로 설정 됩니다. 명확하지 않은 다른 모든 것입니다.
코드가 있었다면 int a[5] = { [2] = 1 }
, 모든 것을 쉽게 했 : 그건가 설정 초기화 지정의 a[2]
에 1
와에 다른 모든 것들 0
. 그러나 { a[2] = 1 }
우리는 할당 표현식을 포함하는 지정되지 않은 이니셜 라이저를 가지고 있으며 우리는 토끼 구멍에 빠지게됩니다.
지금까지 찾은 내용은 다음과 같습니다.
-
a
지역 변수 여야합니다.6.7.8 초기화
- 정적 저장 기간이있는 객체에 대한 이니셜 라이저의 모든 표현식은 상수 표현식 또는 문자열 리터럴이어야합니다.
a[2] = 1
상수 표현식이 아니므로a
자동 저장이 있어야합니다. -
a
자체 초기화의 범위에 있습니다.6.2.1 식별자 범위
- 구조체, 공용체 및 열거 형 태그에는 태그를 선언하는 유형 지정자에서 태그가 나타난 직후에 시작되는 범위가 있습니다. 각 열거 형 상수에는 열거 자 목록에 정의 열거자가 나타난 직후에 시작되는 범위가 있습니다. 다른 식별자에는 선언자가 완료된 직후에 시작되는 범위가 있습니다.
선언자는
a[5]
이므로 변수는 자체 초기화의 범위에 있습니다. -
a
자체 초기화에서 살아 있습니다.6.2.4 객체 저장 기간
-
연결없이 그리고 스토리지 클래스 지정자없이 식별자가 선언 된 객체
static
는 자동 저장 기간을 갖습니다 . -
가변 길이 배열 유형이없는 객체의 경우, 해당 블록의 실행이 어떤 방식 으로든 끝날 때까지 해당 수명이 항목에서 연결된 블록으로 확장됩니다 . (폐쇄 된 블록에 들어가거나 함수를 호출하면 현재 블록의 실행이 중지되지만 종료되지는 않습니다.) 블록이 반복적으로 입력되면 매번 객체의 새 인스턴스가 생성됩니다. 개체의 초기 값이 불확실합니다. 객체에 대해 초기화가 지정되면 블록 실행에서 선언에 도달 할 때마다 초기화가 수행됩니다. 그렇지 않으면 선언에 도달 할 때마다 값이 결정되지 않습니다.
-
-
뒤에 시퀀스 포인트가
a[2]=1
있습니다.6.8 문과 블록
- 전체 표현은 또 다른 표현의 또는 선언자의 일부가 아닌 표현이다. 다음은 각각 완전한 표현식입니다. 이니셜 라이저 ; 표현 문의 표현; 선택문 (
if
또는switch
) 의 제어 표현식while
ordo
문의 제어 표현 ;for
문장 의 각 (선택적) 표현 ;return
명령문 의 (선택적) 표현식 . 전체 표현식의 끝은 시퀀스 포인트입니다.
참고 예에서 것을 부분은 이후에 연속 포인트가 각각의 이니셜 중괄호 둘러싸인 목록이다.
int foo[] = { 1, 2, 3 }
{ 1, 2, 3 }
- 전체 표현은 또 다른 표현의 또는 선언자의 일부가 아닌 표현이다. 다음은 각각 완전한 표현식입니다. 이니셜 라이저 ; 표현 문의 표현; 선택문 (
-
초기화는 이니셜 라이저 목록 순서로 수행됩니다.
6.7.8 초기화
- 각 중괄호로 묶인 이니셜 라이저 목록에는 연결된 현재 객체가 있습니다. 지정이 없으면 현재 개체의 하위 개체는 현재 개체의 유형에 따라 초기화됩니다. 아래 첨자의 배열 요소, 선언 순서의 구조체 멤버, 공용체의 첫 번째 명명 된 멤버입니다. […]
- 초기화는 이니셜 라이저 목록 순서로 발생해야하며, 각 이니셜 라이저는 특정 하위 개체에 대해 제공되며 동일한 하위 개체에 대해 이전에 나열된 이니셜 라이저를 재정의합니다. 명시 적으로 초기화되지 않은 모든 하위 객체는 정적 저장 기간이있는 객체와 동일하게 암시 적으로 초기화됩니다.
-
그러나 이니셜 라이저 표현식이 반드시 순서대로 평가되는 것은 아닙니다.
6.7.8 초기화
- 초기화 목록 표현식간에 부작용이 발생하는 순서는 지정되지 않습니다.
그러나 여전히 몇 가지 질문에 답이 없습니다.
-
시퀀스 포인트도 관련이 있습니까? 기본 규칙은 다음과 같습니다.
6.5 표현
- 이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 객체는 표현식 평가에 의해 최대 한 번 수정 된 저장된 값을 가져야합니다 . 또한 이전 값은 저장 될 값을 결정하기 위해서만 읽어야합니다.
a[2] = 1
은 표현식이지만 초기화는 아닙니다.이것은 Annex J에 의해 약간 모순됩니다.
J.2 정의되지 않은 동작
- 두 시퀀스 포인트 사이에서 객체가 두 번 이상 수정되거나 수정되고 저장 될 값을 결정하는 것 외에 이전 값을 읽습니다 (6.5).
Annex J는 표현식에 의한 수정뿐만 아니라 모든 수정이 중요하다고 말합니다. 그러나 부록이 비 규범 적이라는 점을 감안할 때 우리는 아마도 그것을 무시할 수 있습니다.
-
이니셜 라이저 표현식과 관련하여 하위 객체 초기화는 어떻게 순서가 지정됩니까? 모든 이니셜 라이저가 먼저 평가되고 (어떤 순서로) 하위 객체가 결과와 함께 초기화됩니까 (이니셜 라이저 목록 순서대로)? 아니면 인터리브 될 수 있습니까?
int a[5] = { a[2] = 1 }
다음과 같이 실행된다고 생각 합니다.
a
포함 블록이 입력되면에 대한 스토리지 가 할당됩니다. 이 시점에서 내용은 불확실합니다.- (유일한) 이니셜 라이저가 실행되고 (
a[2] = 1
) 시퀀스 포인트가 이어집니다. 이 상점1
에a[2]
반환1
. - 이는
1
초기화에 사용됩니다a[0]
(첫 번째 이니셜 라이저가 첫 번째 하위 객체를 초기화 함).
그러나 여기 일이 남아있는 요소 (때문에 퍼지 얻을 a[1]
, a[2]
, a[3]
, a[4]
)으로 초기화로되어있다 0
,하지만 때 그것은 분명하지 않다 : 그것은 전에 발생합니까는 a[2] = 1
평가? 그렇다면 a[2] = 1
“승리”하고 덮어 a[2]
쓰지만 0 초기화와 할당 표현식 사이에 시퀀스 포인트가 없기 때문에 할당이 정의되지 않은 동작을 갖습니까? 시퀀스 포인트도 관련이 있습니까 (위 참조)? 아니면 모든 이니셜 라이저가 평가 된 후에 초기화가 발생하지 않습니까? 그렇다면 a[2]
끝나게한다 0
.
C 표준은 여기서 일어나는 일을 명확하게 정의하지 않기 때문에 행동이 정의되지 않았다고 생각합니다 (누락으로 인해).
답변
이해가 안되는데 왜 대신
a[0]
인쇄 합니까?1
0
아마도 먼저 a[2]=1
초기화 a[2]
되고 표현식의 결과는 초기화에 사용됩니다.a[0]
.
N2176 (C17 초안)에서 :
6.7.9 초기화
- 초기화 목록 표현식의 평가는 서로에 대해 불확실하게 순서 가 지정되므로 부작용이 발생하는 순서는 지정되지 않습니다. 154)
따라서 출력이 1 0 0 0 0
도 .
결론 : 초기화 된 변수를 즉시 수정하는 이니셜 라이저를 작성하지 마십시오.
답변
저는 C11 표준이이 동작을 다루고 결과가 명시되지 않았다고 말합니다. 않았다고 . 그리고 C18이이 영역에서 관련 변경 사항을 적용하지 않았다고 생각합니다.
표준 언어는 파싱하기가 쉽지 않습니다. 표준의 관련 섹션은
§6.7.9 초기화 입니다. 구문은 다음과 같이 문서화됩니다.
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designation
opt
initializer
initializer-list , designation
opt
initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
용어 중 하나는 assignment-expression 이며 a[2] = 1
, 확실히 할당 표현식이므로 비 정적 기간을 가진 배열의 이니셜 라이저 내에서 허용됩니다.
§4 정적 또는 스레드 저장 기간이있는 개체에 대한 이니셜 라이저의 모든 식은 상수 식 또는 문자열 리터럴이어야합니다.
주요 단락 중 하나는 다음과 같습니다.
§19 초기화는 이니셜 라이저 목록 순서로 발생해야하며, 각 이니셜 라이저는 동일한 하위 개체에 대해 이전에 나열된 이니셜 라이저를 재정의하는 특정 하위 개체에 제공됩니다. 151)
명시 적으로 초기화되지 않은 모든 하위 객체는 정적 저장 기간이있는 객체와 동일하게 암시 적으로 초기화됩니다.151) 재정의되어 해당 하위 개체를 초기화하는 데 사용되지 않는 하위 개체에 대한 이니셜 라이저는 전혀 평가되지 않을 수 있습니다.
또 다른 핵심 단락은 다음과 같습니다.
§23 초기화 목록 식의 평가는 서로에 대해 불확실하게 순서가 지정되므로 부작용이 발생하는 순서는 지정되지 않습니다. 152)
152) 특히, 평가 순서는 하위 객체 초기화 순서와 같을 필요는 없습니다.
나는 단락 §23이 질문의 표기법을 나타냅니다.
int a[5] = { a[2] = 1 };
지정되지 않은 동작으로 이어집니다. 할당 a[2]
은 부작용이며 표현식의 평가 순서는 서로에 대해 불확실하게 순서가 지정됩니다. 결과적으로 나는 표준에 호소 할 방법이 없다고 생각하고 특정 컴파일러가 이것을 올바르게 또는 잘못 처리하고 있다고 주장합니다.
답변
내 이해는
a[2]=1
값 1을 반환 하므로 코드는
int a[5]={a[2]=1} --> int a[5]={1}
int a[5]={1}
a [0] = 1에 값 할당
따라서 a [0]에 대해 1 을 인쇄합니다 .
예를 들면
char str[10]={‘H’,‘a’,‘i’};
char str[0] = ‘H’;
char str[1] = ‘a’;
char str[2] = ‘i;
답변
나는 퍼즐에 대해 짧고 간단한 대답을하려고합니다. int a[5] = { a[2] = 1 };
- 첫 번째
a[2] = 1
가 설정되었습니다. 즉, 배열에 다음과 같이 표시됩니다.0 0 1 0 0
- 그러나 보라,
{ }
배열을 순서대로 초기화하는 데 사용되는 대괄호로 처리했다면 첫 번째 값 ()을 가져와로1
설정합니다a[0]
. 그것은int a[5] = { a[2] };
우리가 이미 얻은 곳에 남아있는 것처럼 보입니다a[2] = 1
. 결과 배열은 이제 다음과 같습니다.1 0 1 0 0
또 다른 예 : int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
-순서가 다소 임의적이지만 왼쪽에서 오른쪽으로 진행한다고 가정하면 다음 6 단계로 진행됩니다.
0 0 0 1 0 0
1 0 0 1 0 0
1 0 0 1 2 0
1 2 0 1 2 0
1 2 0 1 2 3
1 2 3 1 2 3
답변
할당 a[2]= 1
은 값이있는 표현식 1
이며 기본적으로 썼습니다 int a[5]= { 1 };
(부작용 a[2]
도 할당 1
됨).
답변
나는 그것이 int a[5]={ a[2]=1 };
프로그래머가 자신의 발에 자신을 쏘는 좋은 예 라고 생각 합니다.
나는 당신이 의미하는 바가 int a[5]={ [2]=1 };
C99 지정 이니셜 라이저 설정 요소 2를 1로 설정하고 나머지는 0으로 설정하는 것이라고 생각하고 싶을 수도 있습니다.
당신이 정말로 의미하는 드문 경우에 int a[5]={ 1 }; a[2]=1;
, 그것은 그것을 쓰는 재미있는 방법이 될 것입니다. 어쨌든, 이것은 쓰기 a[2]
가 실제로 실행될 때 잘 정의되지 않았다고 여기에서 지적했지만 코드가 요약 되는 것입니다. 여기서 함정은 a[2]=1
지정된 이니셜 라이저가 아니라 자체 값이 1 인 간단한 할당이라는 것입니다.