programing

C에서 배열 초기화에 대한 혼란

nasanasas 2020. 8. 18. 07:47
반응형

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]인쇄 합니까? 정의되지 않은 동작입니까?10

참고 : 이 질문은 인터뷰에서 요청되었습니다.


요약 : 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 초기화

    1. 정적 저장 기간이있는 객체에 대한 이니셜 라이저의 모든 표현식은 상수 표현식 또는 문자열 리터럴이어야합니다.

    a[2] = 1상수 표현식이 아니므로 a자동 저장이 있어야합니다.

  • a 자체 초기화의 범위에 있습니다.

    6.2.1 식별자 범위

    1. 구조체, 공용체 및 열거 형 태그에는 태그를 선언하는 유형 지정자에서 태그가 나타난 직후에 시작되는 범위가 있습니다. 각 열거 형 상수에는 열거 자 목록에 정의 열거자가 나타난 직후에 시작되는 범위가 있습니다. 다른 식별자에는 선언자가 완료된 직후에 시작되는 범위가 있습니다.

    선언자는 a[5]이므로 변수는 자체 초기화의 범위에 있습니다.

  • a 자체 초기화에서 살아 있습니다.

    6.2.4 객체 저장 기간

    1. 연결없이 그리고 스토리지 클래스 지정자없이 식별자가 선언 된 객체 static자동 저장 기간을 갖습니다 .

    2. 가변 길이 배열 유형이없는 객체의 경우, 해당 블록의 실행이 어떤 방식 으로든 끝날 때까지 해당 수명이 항목에서 연결된 블록으로 확장됩니다 . (폐쇄 된 블록에 들어가거나 함수를 호출하면 현재 블록의 실행이 중단되지만 종료되지는 않습니다.) 블록이 반복적으로 입력되면 매번 객체의 새 인스턴스가 생성됩니다. 개체의 초기 값이 불확실합니다. 객체에 대해 초기화가 지정되면 블록 실행에서 선언에 도달 할 때마다 초기화가 수행됩니다. 그렇지 않으면 선언에 도달 할 때마다 값이 결정되지 않습니다.

  • 뒤에 시퀀스 포인트가 a[2]=1있습니다.

    6.8 문과 블록

    1. 전체 표현은 또 다른 표현의 또는 선언자의 일부가 아닌 표현이다. 다음은 각각 완전한 표현식입니다. 이니셜 라이저 ; 표현 문의 표현; 선택문 ( if또는 switch) 의 제어 표현식 whileor do문의 제어 표현 ; for문장 의 각 (선택적) 표현 ; return명령문 의 (선택적) 표현식 . 전체 표현식의 끝은 시퀀스 포인트입니다.

    참고 예에서 것을 부분은 이후에 연속 포인트가 각각의 이니셜 중괄호 둘러싸인 목록이다.int foo[] = { 1, 2, 3 }{ 1, 2, 3 }

  • 초기화는 이니셜 라이저 목록 순서로 수행됩니다.

    6.7.8 초기화

    1. 각 중괄호로 묶인 이니셜 라이저 목록에는 연결된 현재 객체가 있습니다. 지정이 없으면 현재 개체의 하위 개체는 현재 개체의 유형에 따라 초기화됩니다. 아래 첨자의 배열 요소, 선언 순서의 구조 멤버, 공용체의 첫 번째 명명 된 멤버입니다. [...]

     

    1. 초기화는 이니셜 라이저 목록 순서로 발생해야하며, 각 이니셜 라이저는 동일한 하위 객체에 대해 이전에 나열된 초기화 프로그램을 재정의하는 특정 하위 객체에 제공됩니다. 명시 적으로 초기화되지 않은 모든 하위 객체는 정적 저장 기간이있는 객체와 동일하게 암시 적으로 초기화됩니다.
  • 그러나 이니셜 라이저 표현식이 반드시 순서대로 평가되는 것은 아닙니다.

    6.7.8 초기화

    1. The order in which any side effects occur among the initialization list expressions is unspecified.

However, that still leaves some questions unanswered:

  • Are sequence points even relevant? The basic rule is:

    6.5 Expressions

    1. Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

    a[2] = 1 is an expression, but initialization is not.

    This is slightly contradicted by Annex J:

    J.2 Undefined behavior

    • Between two sequence points, an object is modified more than once, or is modified and the prior value is read other than to determine the value to be stored (6.5).

    Annex J says any modification counts, not just modifications by expressions. But given that annexes are non-normative, we can probably ignore that.

  • How are the subobject initializations sequenced with respect to initializer expressions? Are all initializers evaluated first (in some order), then the subobjects are initialized with the results (in initializer list order)? Or can they be interleaved?


I think int a[5] = { a[2] = 1 } is executed as follows:

  1. Storage for a is allocated when its containing block is entered. The contents are indeterminate at this point.
  2. The (only) initializer is executed (a[2] = 1), followed by a sequence point. This stores 1 in a[2] and returns 1.
  3. That 1 is used to initialize a[0] (the first initializer initializes the first subobject).

But here things get fuzzy because the remaining elements (a[1], a[2], a[3], a[4]) are supposed to be initialized to 0, but it's not clear when: Does it happen before a[2] = 1 is evaluated? If so, a[2] = 1 would "win" and overwrite a[2], but would that assignment have undefined behavior because there is no sequence point between the zero initialization and the assignment expression? Are sequence points even relevant (see above)? Or does zero initialization happen after all initializers are evaluated? If so, a[2] should end up being 0.

Because the C standard does not clearly define what happens here, I believe the behavior is undefined (by omission).


I don't understand, why does a[0] print 1 instead of 0?

Presumably a[2]=1 initializes a[2] first, and the result of the expression is used to initialize a[0].

From N2176 (C17 draft):

6.7.9 Initialization

  1. The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified. 154)

So it would seem that output 1 0 0 0 0 would also have been possible.

Conclusion: Don't write initializers that modifies the initialized variable on the fly.


I think the C11 standard covers this behaviour and says that the result is unspecified, and I don't think C18 made any relevant changes in this area.

The standard language is not easy to parse. The relevant section of the standard is §6.7.9 Initialization. The syntax is documented as:

initializer:
                assignment-expression
                { initializer-list }
                { initializer-list , }
initializer-list:
                designationopt initializer
                initializer-list , designationopt initializer
designation:
                designator-list =
designator-list:
                designator
                designator-list designator
designator:
                [ constant-expression ]
                . identifier

Note that one of the terms is assignment-expression, and since a[2] = 1 is indubitably an assignment expression, it is allowed inside initializers for arrays with non-static duration:

§4 All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

One of the key paragraphs is:

§19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;151) all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.

151) Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.

And another key paragraph is:

§23 The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.152)

152) In particular, the evaluation order need not be the same as the order of subobject initialization.

I'm fairly sure that paragraph §23 indicates that the notation in the question:

int a[5] = { a[2] = 1 };

leads to unspecified behaviour. The assignment to a[2] is a side-effect, and the evaluation order of the expressions are indeterminately sequenced with respect to one another. Consequently, I don't think there is a way to appeal to the standard and claim that a particular compiler is handling this correctly or incorrectly.


My Understanding is a[2]=1 returns value 1 so code becomes

int a[5]={a[2]=1} --> int a[5]={1}

int a[5]={1} assign value for a[0]=1

Hence it print 1 for a[0]

For example

char str[10]={‘H’,‘a’,‘i’};


char str[0] = ‘H’;
char str[1] = ‘a’;
char str[2] = ‘i;

I try to give a short and simple answer for the puzzle: int a[5] = { a[2] = 1 };

  1. First a[2] = 1 is set. That means the array says: 0 0 1 0 0
  2. But behold, given that you did it in the { } brackets, which are used to initialize the array in order, it takes the first value (which is 1) and sets that to a[0]. It is as if int a[5] = { a[2] }; would remain, where we already got a[2] = 1. The resulting array is now: 1 0 1 0 0

Another example: int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 }; - Even though the order is somewhat arbitrary, assuming it goes from left to right, it would go in these 6 steps:

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

The assignment a[2]= 1 is an expression that has the value 1, and you essentially wrote int a[5]= { 1 }; (with the side effect that a[2] is assigned 1 as well).


I believe, that int a[5]={ a[2]=1 }; is a good example for a programmer shooting him/herself into his/her own foot.

I might be tempted to think that what you meant was int a[5]={ [2]=1 }; which would be a C99 designated initializer setting element 2 to 1 and the rest to zero.

In the rare case that you really really meant int a[5]={ 1 }; a[2]=1;, then that would be a funny way of writing it. Anyhow, this is what your code boils down to, even though some here pointed out that it's not well defined when the write to a[2] is actually executed. The pitfall here is that a[2]=1 is not a designated initializer but a simple assignment which itself has the value 1.

참고URL : https://stackoverflow.com/questions/52307474/confusion-about-array-initialization-in-c

반응형