programing

C의 배열 인덱스에 대한 평가 순서(식 대비)

javaba 2022. 8. 27. 21:56
반응형

C의 배열 인덱스에 대한 평가 순서(식 대비)

이 코드를 보면:

static int global_var = 0;

int update_three(int val)
{
    global_var = val;
    return 3;
}

int main()
{
    int arr[5];
    arr[global_var] = update_three(2);
}

갱신되는 어레이 엔트리를 선택합니다.0인가 2인가?

이 경우 C의 사양에 동작의 우선순위를 나타내는 부분이 있습니까?

좌우 오퍼랜드의 순서

에서 할당을 수행하려면arr[global_var] = update_three(2)C 실장은 오퍼랜드를 평가하고 그 부작용으로 왼쪽 오퍼랜드의 저장된 값을 갱신해야 합니다.C 2018 6.5.16(과제에 관한) 단락 3은 좌우 피연산자에 순서가 없음을 알려준다.

피연산자에 대한 평가는 순서가 매겨지지 않습니다.

즉, C 구현에서는 l값을 자유롭게 계산할 수 있습니다. arr[global_var]먼저 ("lvalue"를 표시함) 이 표현식이 무엇을 참조하는지 알아내는 것을 의미하며, 그 다음,update_three(2)마지막으로 후자의 값을 전자에 할당하거나 평가합니다.update_three(2)먼저 l값을 계산한 다음 전자를 후자에 할당하거나 l값을 평가하고update_three(2)적절한 값을 왼쪽 lvalue에 할당합니다.

모든 경우 lvalue에 대한 값 할당은 마지막에 와야 합니다.이는 6.5.16 3에서도 다음과 같이 나타나기 때문입니다.

…왼쪽 피연산자의 저장된 값을 업데이트하는 부작용은 왼쪽 및 오른쪽 피연산자의 값을 계산한 후 시퀀스를 수행합니다.

시퀀스 위반

일부 사용자는 두 가지 사용으로 인해 정의되지 않은 동작에 대해 생각할 수 있습니다.global_var6.5 2를 위반하여 개별적으로 갱신합니다.즉, 다음과 같습니다.

동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 값 계산과 관련하여 스칼라 객체에 대한 부작용이 시퀀스되지 않은 경우 동작은 정의되지 않습니다.

다음과 같은 표현의 동작이 많은 C 실무자에게 매우 친숙합니다.x + x++둘 다 다음 값을 사용하기 때문에 C 표준에서는 정의되지 않습니다.x순서 없이 같은 식에서 개별적으로 수정한다.단, 이 경우 함수 호출이 있어 시퀀싱이 가능합니다. global_var에 사용됩니다.arr[global_var]함수 호출로 갱신됩니다.update_three(2).

6.5.2.2 10은 함수를 호출하기 전에 시퀀스 포인트가 있음을 알려준다.

함수 지정자와 실제 인수 평가 후 실제 호출 전에 시퀀스 포인트가 있습니다.

함수 안에서global_var = val;완전한 표현입니다.3return 3;, 6.8 4:

완전 표현식은 다른 표현식의 일부가 아니며 선언자 또는 추상 선언자의 일부가 아닌 표현식입니다.

다음으로 이들 2개의 식 사이에는 6.8 4에 따라 시퀀스 포인트가 있습니다.

…완전 표현의 평가와 다음 완전 표현의 평가 사이에는 시퀀스 포인트가 있습니다.

따라서 C 구현은 다음을 평가할 수 있습니다.arr[global_var]그 후 함수 호출을 실행합니다.이 경우 함수 호출 전에 시퀀스 포인트가 존재하기 때문에 이들 사이에는 시퀀스 포인트가 존재합니다.그렇지 않으면 이 포인트는 평가될 수 있습니다.global_var = val;함수 호출과 그 다음arr[global_var]이 경우 완전한 표현 뒤에 시퀀스 포인트가 있기 때문에 이들 사이에는 시퀀스 포인트가 있습니다.따라서 동작은 지정되지 않았습니다.두 가지 중 하나를 먼저 평가할 수 있지만 정의되어 있지 않습니다.

여기서의 결과는 불명확합니다.

표현식의 연산 순서(서브 표현식의 그룹화 방법을 지정하는 방법)는 올바르게 정의되어 있지만 평가 순서는 지정되지 않았습니다.이 경우, 이것은 다음 중 하나를 의미합니다.global_var먼저 읽거나 호출할 수 있습니다.update_three먼저 일어날 수도 있지만 어느 쪽인지 알 길이 없어요

여기서 정의되지 않은 동작은 함수 호출에 의해 시퀀스 포인트가 도입되기 때문에 정의되지 않은 동작은 존재하지 않습니다.또한 함수 내의 모든 스테이트먼트가 수정되는 스테이트먼트도 마찬가지입니다.global_var.

명확히 하기 위해, C 표준은 섹션 3.4.3에서 정의되지 않은 동작을 다음과 같이 정의한다.

정의되지 않은 행동

본 국제표준이 요건을 부과하지 않는, 포터블하지 않거나 잘못된 프로그램 구성 또는 잘못된 데이터 사용 시 행동

및 섹션 3.4.4에서 지정되지 않은 동작을 다음과 같이 정의합니다.

불특정 행동

특정되지 않은 값의 사용 또는 본 국제표준이 둘 이상의 가능성을 제공하고 어떠한 경우에도 선택된 추가 요건을 부과하지 않는 기타 행동

이 표준에서는 함수 인수의 평가 순서가 지정되지 않은 것으로 명시되어 있습니다.이 경우 다음 중 하나를 의미합니다.arr[0]3으로 설정되거나arr[2]3으로 설정됩니다.

시도해보니 엔트리가 0으로 업데이트 되었습니다.

그러나 이 질문에 따르면 표현의 오른쪽이 항상 먼저 평가되는가?

평가 순서는 미지정 및 미순서입니다.그래서 저는 이런 암호는 피해야 한다고 생각합니다.

할당할 값을 얻기 전에 할당용 코드를 내보내는 것은 의미가 없기 때문에 대부분의 C 컴파일러는 함수를 호출하는 코드를 먼저 내보내고 결과를 어딘가에 저장합니다(레지스터, 스택 등). 그런 다음 이 값을 최종 수신처에 쓰는 코드를 내보냅니다.따라서 ch가 끝나면 글로벌 변수를 읽습니다.aged. 이것을 "자연질서"라고 부르자. 어떤 표준이 아니라 순수한 논리로 정의된다.

그러나 최적화 과정에서 컴파일러는 값을 일시적으로 저장하는 중간 단계를 없애고 가능한 한 직접 함수 결과를 최종 목적지에 쓰려고 할 것입니다.이 경우 함수 결과를 직접 이동시키기 위해 먼저 인덱스를 읽어야 하는 경우가 많습니다.어레이에 접속합니다.이로 인해 글로벌 변수가 변경되기 전에 읽힐 수 있습니다.

이는 기본적으로 매우 나쁜 속성을 가진 정의되지 않은 동작으로 최적화가 수행되는지 여부 및 이 최적화가 얼마나 적극적인지에 따라 결과가 달라질 수 있습니다.다음 중 하나의 코딩으로 이 문제를 해결하는 것은 개발자로서 해야 할 일입니다.

int idx = global_var;
arr[idx] = update_three(2);

또는 코드화:

int temp = update_three(2);
arr[global_var] = temp;

경험에 비추어 볼 때:글로벌 변수가 다음과 같은 경우를 제외하고const(혹은 그렇지 않지만 어떤 코드도 그것들을 부작용으로 바꾸지 않는다는 것을 알고 있습니다), 멀티 스레드 환경에서와 같이 코드로 직접 사용하지 마십시오.이것조차도 정의되지 않을 수 있습니다.

int result = global_var + (2 * global_var);
// Is not guaranteed to be equal to `3 * global_var`!

컴파일러는 두 번 읽을 수 있고 다른 스레드는 두 읽기 사이의 값을 변경할 수 있습니다.그러나 최적화로 인해 코드가 한 번만 읽게 되므로 다른 스레드의 타이밍에 따라 다른 결과가 나타날 수 있습니다.따라서 글로벌 변수를 사용하기 전에 임시 스택 변수에 저장하면 두통이 훨씬 줄어듭니다.컴파일러가 이것이 안전하다고 생각하는 경우, 컴파일러는 그마저도 최적화하고 대신 글로벌 변수를 직접 사용하기 때문에 결국 성능이나 메모리 사용에 차이가 없을 수 있습니다.

(누군가 왜 그러냐고 물으면x + 2 * x대신3 * x- CPU에 따라서는 덧셈이 초고속이며 컴파일러가 비트 시프트로 변환하기 때문에 곱셈도 2가 됩니다.2 * x == x << 1단, 임의의 숫자에 의한 곱셈은 매우 느릴 수 있습니다.따라서 3을 곱하고 적극적인 최적화를 활성화하면 3을 곱하는 대신 3을 곱하면 x를 곱하는 것보다 훨씬 빠른 코드를 얻을 수 있습니다.또한 이 트릭도 최신 컴파일러에 의해 실행됩니다.st 그 이후로는 트릭으로 인해 계산이 느려집니다.)

글로벌 에디트: 죄송해요, 제가 너무 흥분해서 말도 안 되는 글을 많이 썼어요.늙은 괴짜가 고함을 질렀을 뿐이야

C는 무사했다고 믿고 싶었지만, C11부터 C++와 대등하게 되었습니다.분명히 컴파일러가 식에서 부작용에 대해 무엇을 할 것인지 아는 것은 이제 "동기점 앞에 위치"에 기초한 코드 시퀀스의 부분적인 순서와 관련된 작은 수학 수수께끼를 풀어야 합니다.

나는 다시 K&amp에는 몇가지 중요한 실시간 임베디드 시스템 구현되도록 설계했다고 하는데 일어나(전기 차의 엔진이 수표, 적당히지 않는 과육을 사람들을 짓누를 수 있는 10톤 산업용 로봇 및 시스템의 지켜지지 않았은 사람들이 가장 가까운 벽에 충돌해 줄 수 있는 컨트롤러 등 R일.은 후 그는 t수십 개의 프로세서가 데이터 버스를 완전히 흡인하여 시스템 오버헤드가 1% 미만일 수 있습니다.)

미정의와 미지정의 차이를 이해하기에는 너무 노망스럽거나 멍청할 수도 있지만, 동시 실행과 데이터 액세스의 의미에 대해서는 아직 잘 알고 있다고 생각합니다.내 의견으로는 C++와 현재 C남자가 자신의 애완용 언어를 사용하는 것에 대한 집착은 비용이 많이 드는 헛된 꿈이다.당신은 동시 처형이 무엇인지 알고 있고, 당신은 이 장비들 중 어떤 것도 필요없거나, 아니면 필요없거나, 그리고 당신은 그것을 망치려고 하지 않는 것을 세상에 널리 베풀 것이다.

눈길을 끄는 메모리 장벽 추상화는 모두 멀티 CPU 캐시 시스템의 일시적인 제한에 의한 것으로, 예를 들어 C++가 제공하는 뮤텍스나 조건 변수와 같은 공통 OS 동기 오브젝트에 모두 안전하게 캡슐화할 수 있습니다.
이 캡슐화의 비용은 특정 CPU 명령어를 세세하게 사용하면 달성할 수 있는 것에 비해 퍼포먼스가 약간 저하되는 경우가 있습니다.
volatile키워드(또는 a)#pragma dont-mess-with-that-variable컴파일러에게 메모리 액세스의 순서를 변경하는 것을 멈추도록 지시하기에 충분한 시스템 프로그래머로서 주의를 기울입니다.asm의 다이렉트 디렉티브에 의해, 저레벨의 드라이버와 OS 코드에 애드혹의 CPU 고유의 커맨드를 살포하는 것으로, 최적인 코드를 간단하게 작성할 수 있습니다.기본 하드웨어(캐시 시스템 또는 버스 인터페이스)가 어떻게 작동하는지 잘 알지 못하면 쓸모없거나 비효율적이거나 결함이 있는 코드를 쓰게 됩니다.

의 미세 조정volatile키워드와 밥은 하급 프로그래머들의 삼촌을 제외하고는 모두였을 것이다.대신, 일반적인 C++ 수학 괴짜들은 존재하지 않는 문제를 찾고 프로그래밍 언어의 정의를 컴파일러의 사양으로 오인하여 또 다른 이해할 수 없는 추상화를 설계하는 데 어려움을 겪었습니다.

이번 변경은 C의 근본적인 측면을 없애기 위해서도 필요했습니다.왜냐하면, 이러한 「배리어」는, Low Level C 코드에서도 생성되어야만 올바르게 동작할 수 있었기 때문입니다.그것은 무엇보다도 표현의 정의에 아무런 설명도 정당성도 없이 대혼란을 일으켰다.

결론적으로 컴파일러가 이 터무니없는 C의 조각으로부터 일관된 기계 코드를 생성할 수 있다는 사실은 C++가 2000년대 후반의 캐시 시스템의 잠재적인 불일치에 대처하는 방법의 먼 결과일 뿐이다.
그것은 C의 하나의 기본적인 측면을 엉망으로 만들었다. 그래서 대부분의 C 프로그래머들은 - 캐시 시스템에 대해 전혀 신경 쓰지 않고, 그리고 당연히 그렇다 - 이제 - 사이의 차이를 설명하기 위해 전문가에게 의존할 수 밖에 없다.a = b() + c() and a = b + c.

Trying to guess what will become of this unfortunate array is a net loss of time and efforts anyway. Regardless of what the compiler will make of it, this code is pathologically wrong. The only responsible thing to do with it is send it to the bin.
Conceptually, side effects can always be moved out of expressions, with the trivial effort of explicitly letting the modification occur before or after the evaluation, in a separate statement.
This kind of shitty code might have been justified in the 80's, when you could not expect a compiler to optimize anything. But now that compilers have long become more clever than most programmers, all that remains is a piece of shitty code.

I also fail to understand the importance of this undefined / unspecified debate. Either you can rely on the compiler to generate code with a consistent behaviour or you can't. Whether you call that undefined or unspecified seems like a moot point.

In my arguably informed opinion, C is already dangerous enough in its K&R state. A useful evolution would be to add common sense safety measures. For instance, making use of this advanced code analysis tool the specs force the compiler to implement to at least generate warnings about bonkers code, instead of silently generating a code potentially unreliable to the extreme.
But instead the guys decided, for instance, to define a fixed evaluation order in C++17. Now every software imbecile is actively incited to put side effects in his/her code on purpose, basking in the certainty that the new compilers will eagerly handle the obfuscation in a deterministic way.

K&R was one of the true marvels of the computing world. For twenty bucks you got a comprehensive specification of the language (I've seen single individuals write complete compilers just using this book), an excellent reference manual (the table of contents would usually point you within a couple of pages of the answer to your question), and a textbook that would teach you to use the language in a sensible way. Complete with rationales, examples and wise words of warning about the numerous ways you could abuse the language to do very, very stupid things.

적은 이익을 위해 그 유산을 파괴하는 건 내게는 잔인한 낭비인 것 같아.하지만 다시 말하지만 나는 요점을 완전히 파악하지 못할 수도 있다.어쩌면 어떤 친절한 사람이 이런 부작용을 이용하는 새로운 C코드의 예를 들어줄 수 있지 않을까?

언급URL : https://stackoverflow.com/questions/59722807/order-of-evaluation-of-array-indices-versus-the-expression-in-c

반응형