[c#] “k + = c + = k + = c;”에 인라인 연산자에 대한 설명이 있습니까?

다음 작업의 결과에 대한 설명은 무엇입니까?

k += c += k += c;

다음 코드의 출력 결과를 이해하려고했습니다.

int k = 10;
int c = 30;
k += c += k += c;
//k=80 instead of 110
//c=70

현재 “k”의 결과가 80 인 이유를 이해하는 데 어려움을 겪고 있습니다. k = 40 할당이 작동하지 않는 이유는 무엇입니까 (실제로 Visual Studio는 해당 값이 다른 곳에서 사용되지 않는다고 알려줍니다)?

왜 110이 아닌 k 80입니까?

작업을 다음으로 분할하면 :

k+=c;
c+=k;
k+=c;

결과는 k = 110입니다.

CIL 을 살펴 보려고 했지만 생성 된 CIL을 해석하는 데 그다지 심오하지 않으며 몇 가지 세부 정보를 얻을 수 없습니다.

 // [11 13 - 11 24]
IL_0001: ldc.i4.s     10
IL_0003: stloc.0      // k

// [12 13 - 12 24]
IL_0004: ldc.i4.s     30
IL_0006: stloc.1      // c

// [13 13 - 13 30]
IL_0007: ldloc.0      // k expect to be 10
IL_0008: ldloc.1      // c
IL_0009: ldloc.0      // k why do we need the second load?
IL_000a: ldloc.1      // c
IL_000b: add          // I expect it to be 40
IL_000c: dup          // What for?
IL_000d: stloc.0      // k - expected to be 40
IL_000e: add
IL_000f: dup          // I presume the "magic" happens here
IL_0010: stloc.1      // c = 70
IL_0011: add
IL_0012: stloc.0      // k = 80??????



답변

같은 작업 a op= b;a = a op b;. 할당은 문이나 식으로 사용할 수 있지만 식으로 할당 된 값을 생성합니다. 귀하의 진술 …

k += c += k += c;

… 할당 연산자는 오른쪽 연관이므로 다음과 같이 작성할 수도 있습니다.

k += (c += (k += c));

또는 (확장 됨)

k =  k +  (c = c +  (k = k  + c));
     10301030   // operand evaluation order is from left to right
      |         |        ↓    ↓
      |         ↓   4010 + 30   // operator evaluation7030 + 40
8010 + 70

전체 평가 중에 관련된 변수의 이전 값이 사용되는 곳. 이것은 특히 가치에 대해 사실입니다 k(아래 IL에 대한 리뷰 및 Wai Ha Lee가 제공 한 링크 참조 ). 따라서 70 + 40 (새 값 k) = 110이 아니라 70 + 10 (이전 값 k) = 80이됩니다.

요점은 (C # 사양 에 따라 ) “표현식 의 피연산자는 왼쪽에서 오른쪽으로 평가됩니다” (피연산자는 변수 c이며 k우리의 경우)입니다. 이것은이 경우 오른쪽에서 왼쪽으로 실행 순서를 지시하는 연산자 우선 순위 및 연관성과는 무관합니다. ( 이 페이지에서 Eric Lippert의 답변대한 의견을 참조 하십시오).


이제 IL을 살펴 보겠습니다. IL은 스택 기반 가상 머신을 가정합니다. 즉, 레지스터를 사용하지 않습니다.

IL_0007: ldloc.0      // k (is 10)
IL_0008: ldloc.1      // c (is 30)
IL_0009: ldloc.0      // k (is 10)
IL_000a: ldloc.1      // c (is 30)

이제 스택은 다음과 같습니다 (왼쪽에서 오른쪽으로, 스택의 맨 위가 오른쪽).

10 30 10 30

IL_000b: add          // pops the 2 top (right) positions, adds them and pushes the sum back

10 30 40

IL_000c: dup

10 30 40 40

IL_000d: stloc.0      // k <-- 40

10 30 40

IL_000e: add

10 70

IL_000f: dup

10 70 70

IL_0010: stloc.1      // c <-- 70

10 70

IL_0011: add

80

IL_0012: stloc.0      // k <-- 80

참고 IL_000c: dup, IL_000d: stloc.0즉로 첫 임무 k , 멀리 최적화 할 수 있습니다. 아마도 이것은 IL을 기계어 코드로 변환 할 때 지터에 의해 변수에 대해 수행됩니다.

또한 계산에 필요한 모든 값은 할당되기 전에 스택에 푸시되거나 이러한 값에서 계산됩니다. (기준 할당 된 값 stloc)이 평가하는 동안 다시 사용되지 않습니다. stloc스택의 맨 위를 팝니다.


다음 콘솔 테스트의 출력은 ( Release최적화 가 설정된 모드)입니다.

k 평가 (10)
c 평가 (30)
k 평가 (10) k
평가 (30)
40 k에
할당 70 c에
할당 80 k에 할당

private static int _k = 10;
public static int k
{
    get { Console.WriteLine($"evaluating k ({_k})"); return _k; }
    set { Console.WriteLine($"{value} assigned to k"); _k = value; }
}

private static int _c = 30;
public static int c
{
    get { Console.WriteLine($"evaluating c ({_c})"); return _c; }
    set { Console.WriteLine($"{value} assigned to c"); _c = value; }
}

public static void Test()
{
    k += c += k += c;
}


답변

우선, Henk와 Olivier의 대답이 맞습니다. 조금 다른 방식으로 설명하고 싶습니다. 특히이 점을 말씀 드리고 싶습니다. 다음과 같은 문장이 있습니다.

int k = 10;
int c = 30;
k += c += k += c;

그런 다음이 명령문 세트와 동일한 결과를 제공해야한다고 잘못 결론을 내립니다.

int k = 10;
int c = 30;
k += c;
c += k;
k += c;

당신이 어떻게 잘못했는지, 그리고 그것을 올바르게하는 방법을 보는 것은 유익합니다. 그것을 분해하는 올바른 방법은 다음과 같습니다.

먼저 가장 바깥 쪽 + =를 다시 작성하십시오.

k = k + (c += k += c);

둘째, 가장 바깥 쪽 +를 다시 작성하십시오. x = y + z는 항상 “y를 임시로 평가하고, z를 임시로 평가하고, 임시를 합하고, 합계를 x에 할당”과 동일해야한다는 데 동의하기를 바랍니다 . 그래서 그것을 매우 명시 적으로 만들어 보겠습니다.

int t1 = k;
int t2 = (c += k += c);
k = t1 + t2;

이것은 당신이 잘못한 단계이기 때문에 명확하게 확인하십시오 . 복잡한 작업을 더 간단한 작업으로 나눌 때 천천히 조심스럽게 수행 하고 단계를 건너 뛰지 않도록해야합니다 . 단계를 건너 뛰는 것은 우리가 실수하는 곳입니다.

이제 t2에 대한 할당을 다시 천천히 조심스럽게 분해합니다.

int t1 = k;
int t2 = (c = c + (k += c));
k = t1 + t2;

할당은 c에 할당 된 것과 동일한 값을 t2에 할당하므로 다음과 같이 가정 해 보겠습니다.

int t1 = k;
int t2 = c + (k += c);
c = t2;
k = t1 + t2;

큰. 이제 두 번째 줄을 나눕니다.

int t1 = k;
int t3 = c;
int t4 = (k += c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

좋습니다. 우리는 발전하고 있습니다. t4에 대한 할당을 분류합니다.

int t1 = k;
int t3 = c;
int t4 = (k = k + c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

이제 세 번째 줄을 나눕니다.

int t1 = k;
int t3 = c;
int t4 = k + c;
k = t4;
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

이제 우리는 모든 것을 볼 수 있습니다.

int k = 10;  // 10
int c = 30;  // 30
int t1 = k;  // 10
int t3 = c;  // 30
int t4 = k + c; // 40
k = t4;         // 40
int t2 = t3 + t4; // 70
c = t2;           // 70
k = t1 + t2;      // 80

완료되면 k는 80이고 c는 70입니다.

이제 이것이 IL에서 어떻게 구현되는지 살펴 보겠습니다.

int t1 = k;
int t3 = c;
  is implemented as
ldloc.0      // stack slot 1 is t1
ldloc.1      // stack slot 2 is t3

이제 이것은 약간 까다 롭습니다.

int t4 = k + c;
k = t4;
  is implemented as
ldloc.0      // load k
ldloc.1      // load c
add          // sum them to stack slot 3
dup          // t4 is stack slot 3, and is now equal to the sum
stloc.0      // k is now also equal to the sum

위의 내용을 다음과 같이 구현할 수 있습니다.

ldloc.0      // load k
ldloc.1      // load c
add          // sum them
stloc.0      // k is now equal to the sum
ldloc.0      // t4 is now equal to k

그러나 “dup”트릭을 사용하면 코드가 짧아지고 지터가 더 쉬워지기 때문에 동일한 결과를 얻습니다. 일반적으로 C # 코드 생성기는 스택에서 가능한 한 임시를 “임시”로 유지하려고합니다. 일시적인 횟수를 줄 이면서 IL을 따르는 것이 더 쉽다면 최적화를 끄면 코드 생성기가 덜 공격적입니다.

이제 c를 얻기 위해 동일한 트릭을 수행해야합니다.

int t2 = t3 + t4; // 70
c = t2;           // 70
  is implemented as:
add          // t3 and t4 are the top of the stack.
dup
stloc.1      // again, we do the dup trick to get the sum in 
             // both c and t2, which is stack slot 2.

그리고 마지막으로:

k = t1 + t2;
  is implemented as
add          // stack slots 1 and 2 are t1 and t2.
stloc.0      // Store the sum to k.

우리는 다른 것에 대한 합계가 필요하지 않기 때문에 그것을 복제하지 않습니다. 이제 스택이 비어 있으며 명령문의 끝입니다.

이야기의 교훈은 다음과 같습니다. 복잡한 프로그램을 이해하려고 할 때 항상 한 번에 하나씩 작업을 세분화하십시오 . 지름길을 사용하지 마십시오. 그들은 당신을 타락하게 할 것입니다.


답변

이것은 다음과 같이 요약됩니다. +=원본에 가장 먼저 적용 k되는가 아니면 오른쪽으로 더 많이 계산 된 값에 적용 됩니까?

대답은 할당이 오른쪽에서 왼쪽으로 바인딩되지만 작업은 여전히 ​​왼쪽에서 오른쪽으로 진행된다는 것입니다.

그래서 가장 왼쪽 +=10 += 70.


답변

나는 gcc와 pgcc로 예제를 시도했고 110을 얻었다. 나는 그들이 생성 한 IR을 확인했고 컴파일러는 expr을 다음과 같이 확장했다.

k = 10;
c = 30;
k = c+k;
c = c+k;
k = c+k;

나에게 합리적으로 보입니다.


답변

이러한 종류의 체인 할당의 경우 가장 오른쪽에서 시작하여 값을 할당해야합니다. 할당하고 계산하고 왼쪽에 할당해야하며 마지막 (가장 왼쪽 할당)까지 계속 진행해야합니다. 물론 k = 80으로 계산됩니다.


답변

간단한 대답 : vars를 값으로 바꾸면 얻을 수 있습니다.

int k = 10;
int c = 30;
k += c += k += c;
10 += 30 += 10 += 30
= 10 + 30 + 10 + 30
= 80 !!!


답변

세어 보면 해결할 수 있습니다.

a = k += c += k += c

두 개의 cs와 두 개의 ks가 있으므로

a = 2c + 2k

그리고 언어 연산자의 결과로 다음과 k같습니다.2c + 2k

이것은이 체인 스타일의 변수 조합에 대해 작동합니다.

a = r += r += r += m += n += m

그래서

a = 2m + n + 3r

그리고 r동일합니다.

가장 왼쪽 할당까지만 계산하여 다른 숫자의 값을 계산할 수 있습니다. 그래서 m동일 2m + nn같습니다 n + m.

이것은 그것이 k += c += k += c;다른 k += c; c += k; k += c;이유와 다른 대답을 얻는 이유를 보여줍니다 .

댓글의 일부 사람들은이 단축키에서 가능한 모든 유형의 추가로 지나치게 일반화하려고 할 수 있다고 걱정하는 것 같습니다. 따라서이 단축키는이 상황에만 적용 할 수 있음을 분명히 할 것입니다. 즉, 내장 된 숫자 유형에 대한 추가 할당을 함께 연결하는 것입니다. 예를 들어 ()또는 에 다른 연산자를 추가 +하거나 함수를 호출하거나 재정의 한 +=경우 또는 기본 숫자 유형이 아닌 다른 것을 사용하는 경우 에는 (필연적으로) 작동하지 않습니다 . 질문의 특정 상황에 도움을주기위한 것 입니다.