2009-08-27 2 views
7

Visual C++ 2008에서이 코드를 시도한 결과 A와 B가 같은 주소를 가지고 있지 않음을 보여줍니다. C++ 스택 및 범위

int main() 
{ 
    { 
     int A; 
     printf("%p\n", &A); 
    } 

    int B; 
    printf("%p\n", &B); 
} 

그러나 B가 정의됩니다 때 더 이상 존재하지 않기 때문에

, 컴파일러하지 않는 이유

가 이해가 안 ... 같은 스택 위치를 재사용 할 수 있다는 것을 나에게 보인다 매우 간단한 최적화 (더 큰 변수와 재귀 함수의 컨텍스트에서 중요 할 수 있음)처럼 보이는 것을 수행하는 것처럼 보입니다. 그리고 재사용하는 것이 CPU 나 메모리에서 더 무거워지는 것 같지 않습니다. 누구든지 이것에 대한 설명이 있습니까?

나는 "그것이보기보다 훨씬 더 복잡이기 때문에"대답은의 라인을 따라 추측하지만, 솔직히 아무 생각이 없습니다.

편집 : 아래의 답변과 의견에 대한 일부 정밀도.

이 코드의 문제는이 함수가 호출 될 때마다, 스택은 "너무 많은 일 개 정수를"성장한다는 것입니다. 물론이 예제에서는 아무런 문제가 없지만 큰 변수와 재귀 호출을 고려하면 쉽게 피할 수있는 스택 오버플로가 있습니다.

내가 제안하는 것은 메모리 최적화이지만 성능에 어떤 영향을 미치는지는 알 수 없다.

그런데 이것은 에서 발생합니다. 빌드는 모든 최적화를 수행합니다.

+0

릴리스 빌드 또는 디버그 빌드를 컴파일하고 있습니까? – Michael

+1

당신이 제안하는 것은 ** 공간 ** 최적화이지만 반드시 속도 최적화는 아닙니다. –

+1

모든 지역이 캐시 라인에 맞추기에는 너무 크면 캐시 미스가 없으므로 속도 최적화가됩니다. – Michael

답변

8

이렇게 지역 주민을 위해 스택 공간을 재사용하는 것은 매우 일반적인 최적화입니다. 실제로 최적화 된 빌드에서 지역 주민의 주소를 사용하지 않으면 컴파일러는 스택 공간을 할당하지 않을 수도 있으며 변수는 레지스터에만 저장됩니다.

이 최적화는 여러 가지 이유로 표시되지 않을 수 있습니다.

우선 디버그 빌드와 같이 최적화가 해제 된 경우 컴파일러는 디버깅을 쉽게하기 위해 이들 중 하나를 수행하지 않습니다. 함수에서 더 이상 사용되지 않더라도 A의 값을 볼 수 있습니다.

최적화를 사용하여 컴파일하는 경우 로컬 주소를 가져 와서 다른 함수로 전달하기 때문에 컴파일러는 해당 함수가 수행하는 작업이 명확하지 않기 때문에 저장소를 다시 사용하지 않으려 고합니다. 주소와.

하나는 함수가 사용하는 스택 공간이 어떤 임계 값을 초과하지 않는 한이 최적화를 사용하지 않을 컴파일러를 상상할 수있다. 더 이상 사용되지 않는 지역 변수의 공간을 재사용하면 비용이 0이되고 보드 전체에 적용될 수 있으므로이 작업을 수행하는 컴파일러에 대해서는 알지 못합니다.

스택 성장 즉, 당신이 스택 오버 플로우를 타격하는 일부 시나리오에서는, 당신은 스택 공간의 컴파일러의 최적화에 의존해서는 안 응용 프로그램에 대한 심각한 우려가있는 경우. 스택의 큰 버퍼를 힙으로 이동하고 매우 깊은 재귀를 제거하는 작업을 고려해야합니다. 예를 들어 Windows 스레드의 경우 기본적으로 1MB 스택이 있습니다.각 스택 프레임에 1k의 메모리를 할당하고 1000 회 이상의 재귀 호출을 수행하기 때문에 오버플로가 우려된다면 각 스택 프레임에서 일부 공간을 절약하기 위해 컴파일러를 동축 시키려고하지 않는 것이 좋습니다.

+0

위의 예제에서 레지스터에 A와 B를 넣을 수 없다고 생각한다. 왜냐하면 & A & B가 필요합니다. – AraK

+0

@Arak - 맞습니다. 그래서 내가 "당신이 주소를 모르면"라고 말한 것입니다. – Michael

+0

@Michael, 죄송합니다. 나는 그것을 보지 못했습니다. – AraK

1

그것은 컴파일러가 동일한 스택 프레임에 모두을 가하고 것이 아마. 따라서 A가 범위 외부에서 액세스 할 수 없더라도 컴파일러는 코드의 의미를 손상시키지 않으면 서 메모리에있는 장소에 자유롭게 바인딩 할 수 있습니다. 요약하면, 그들은 당신이 메인을 실행할 때 동시에 스택에 놓이게됩니다.

+2

다음으로도 알려져 있습니다. 컴파일러보다 더 똑똑해 지려고하지 마십시오. – ebo

+0

나는 컴파일러가 그것을 할 수 있다는 것을 알고있다. 그러나 나는 왜 그가 그 일을하는지 궁금해한다. – Drealmer

1

A는 B. 이후 스택 에 할당됩니다. B는 코드에서 A 다음에 선언되지만 (이는 C90에서 허용하지 않음) 여전히 주 기능의 최상위 범위에 있으므로 메인 틸의 끝 부분. 따라서 B가 메인이 시작되면 푸시되고 A는 내부 스코프가 입력되면 푸시되고 왼쪽으로는 팝되면 메인 기능이 남아있을 때 B가 팝됩니다.

+1

컴파일러는 생성자/소멸자가 올바른 순서와 위치에서 실행되는 한 스택에서 임의의 순서로 A와 B를 할당 할 수 있습니다. –

+0

그들은 스택에있을 필요조차 없습니다. 주소가 위의 예제에서 취해지지 않았다면, 컴파일러는 그것들을 레지스터에 남겨두고 함수는 스택 공간을 0으로 취할 수 있습니다. – Michael

3

왜 어셈블리를 체크 아웃하지 않으시겠습니까?

코드를 약간 변경하여 int A = 1; 및 int B = 2; 해독하기가 약간 더 쉬워졌습니다. 기본 설정

++ g에서

:

컴파일러는 단지 같은 주소에 넣어 귀찮게하지 않았다처럼
.globl main 
    .type main, @function 
main: 
.LFB2: 
    leal 4(%esp), %ecx 
.LCFI0: 
    andl $-16, %esp 
    pushl -4(%ecx) 
.LCFI1: 
    pushl %ebp 
.LCFI2: 
    movl %esp, %ebp 
.LCFI3: 
    pushl %ecx 
.LCFI4: 
    subl $36, %esp 
.LCFI5: 
    movl $1, -8(%ebp) 
    leal -8(%ebp), %eax 
    movl %eax, 4(%esp) 
    movl $.LC0, (%esp) 
    call printf 
    movl $2, -12(%ebp) 
    leal -12(%ebp), %eax 
    movl %eax, 4(%esp) 
    movl $.LC0, (%esp) 
    call printf 
    movl $0, %eax 
    addl $36, %esp 
    popl %ecx 
    popl %ebp 
    leal -4(%ecx), %esp 
    ret 
.LFE2: 

는 궁극적으로 보인다. 멋진 lookahead 최적화가 필요 없습니다. 최적화를 시도하지 않았거나 이점이 없다고 결정했습니다.

알림 A가 할당 된 다음 인쇄됩니다. 그런 다음 원본 소스와 마찬가지로 B가 지정되고 인쇄됩니다. 물론 다른 컴파일러 설정을 사용하면 완전히 다르게 보일 수 있습니다. 내 지식 B위한 공간으로

2

는 그 라인 전에 디버거에서 휴식 경우, 그럼에도 불구하고의 주소를 얻을 수있는 라인

int B; 

에 주에 항목에 예약되지 않으며, 스택 포인터도이 줄 다음에 변경되지 않습니다. 이 줄에서 일어나는 유일한 일은 B의 생성자가 호출된다는 것입니다.

+0

C++ 표준에는 이것을 금지하거나 허용하는 것이 없습니다. 따라서 컴파일러가 실제로 B에 대한 메모리를 할당하는 것이 가능합니다. 다른 컴파일러는 그렇지 않습니다. – MSalters

-1

이 경우 컴파일러에는 실제로 선택의 여지가 없습니다. printf()의 특정 동작을 가정 할 수 없습니다. 따라서 A 자체가 존재하는 한 &Aprintf()이 걸릴 수 있다고 가정해야합니다. 따라서 A 자체는 정의 된 전체 범위에 있습니다.

+2

-1 : *'A' 자체가 정의 된 전체 범위에 살고 있습니다 *. 이것이 바로 {{}} 구조에있는 이유입니다. 그래서 컴파일러가'B'를 만났을 때 외부에서 정의되지 않았습니다. 따라서 컴파일러 **에는 ** 선택 사항이 있습니다. –

1

제 일의 큰 부분은 컴파일러와 싸우는 것이고, 우리는 인간이하는 일을 항상 기대하지는 않는다고 말해야합니다. 컴파일러를 프로그래밍 했더라도 여전히 결과에 놀랄 수 있습니다. 입력 행렬은 100 %를 예측하는 것이 불가능합니다.

컴파일러의 최적화 부분은 매우 복잡하며 다른 답변에서 언급 한 것처럼 설정에 대한 자발적 응답으로 인해 발생할 수 있지만 주변 코드의 영향을받을 수 있습니다. 논리에서 이러한 최적화가 이루어지지 않을 수도 있습니다.

Micheal이 말했듯이, 스택 오버플로를 방지하기 위해 컴파일러에 의존해서는 안됩니다. 보통의 코드 유지 관리 나 다른 입력 집합이 사용될 때 나중에 문제를 푸시 할 수 있기 때문입니다. 파이프 라인에서 훨씬 더 추락 할 것입니다. 아마도 사용자의 손에 있습니다.