다음 작업의 결과에 대한 설명은 무엇입니까?
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));
10 → 30 → 10 → 30 // operand evaluation order is from left to right
| | ↓ ↓
| ↓ 40 ← 10 + 30 // operator evaluation
↓ 70 ← 30 + 40
80 ← 10 + 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
두 개의 c
s와 두 개의 k
s가 있으므로
a = 2c + 2k
그리고 언어 연산자의 결과로 다음과 k
같습니다.2c + 2k
이것은이 체인 스타일의 변수 조합에 대해 작동합니다.
a = r += r += r += m += n += m
그래서
a = 2m + n + 3r
그리고 r
동일합니다.
가장 왼쪽 할당까지만 계산하여 다른 숫자의 값을 계산할 수 있습니다. 그래서 m
동일 2m + n
과 n
같습니다 n + m
.
이것은 그것이 k += c += k += c;
다른 k += c; c += k; k += c;
이유와 다른 대답을 얻는 이유를 보여줍니다 .
댓글의 일부 사람들은이 단축키에서 가능한 모든 유형의 추가로 지나치게 일반화하려고 할 수 있다고 걱정하는 것 같습니다. 따라서이 단축키는이 상황에만 적용 할 수 있음을 분명히 할 것입니다. 즉, 내장 된 숫자 유형에 대한 추가 할당을 함께 연결하는 것입니다. 예를 들어 ()
또는 에 다른 연산자를 추가 +
하거나 함수를 호출하거나 재정의 한 +=
경우 또는 기본 숫자 유형이 아닌 다른 것을 사용하는 경우 에는 (필연적으로) 작동하지 않습니다 . 질문의 특정 상황에 도움을주기위한 것 입니다.