답변:
아니요, 괄호는 스택 프레임으로 작동하지 않습니다. C에서 중괄호는 이름 지정 범위 만 나타내지 만 제어가 범위를 벗어나면 아무것도 파괴되거나 스택에서 튀어 나오는 것이 없습니다.
프로그래머가 코드를 작성하면 종종 스택 프레임 인 것처럼 생각할 수 있습니다. 중괄호 안에 선언 된 식별자는 중괄호 내에서만 액세스 할 수 있으므로 프로그래머의 관점에서 볼 때, 선언 될 때 스택으로 푸시 된 다음 범위가 종료 될 때 팝업되는 것과 같습니다. 그러나 컴파일러는 진입 / 종료시 어떤 것도 푸시 / 팝핑하는 코드를 생성 할 필요가 없으며 일반적으로 그렇지 않습니다.
또한 지역 변수는 스택 공간을 전혀 사용하지 않을 수 있습니다. CPU 레지스터 나 다른 보조 기억 장치 위치에 유지되거나 완전히 최적화 될 수 있습니다.
따라서 d
이론적으로 배열은 전체 기능을 위해 메모리를 소비 할 수 있습니다. 그러나 컴파일러는이를 최적화하거나 사용 수명이 겹치지 않는 다른 로컬 변수와 메모리를 공유 할 수 있습니다.
변수가 실제로 메모리를 차지하는 시간 은 분명히 컴파일러에 따라 다릅니다 (그리고 많은 컴파일러는 내부 블록이 함수 내에서 들어오고 나올 때 스택 포인터를 조정하지 않습니다).
그러나 밀접하게 관련되어 있지만 더 흥미로운 질문은 프로그램이 내부 범위 외부 (그러나 포함 함수 내)의 내부 객체에 액세스 할 수 있는지 여부입니다.
void foo() {
int c[100];
int *p;
{
int d[200];
p = d;
}
/* Can I access p[0] here? */
return;
}
(즉 , 실제로는 그렇지 않은 경우에도 컴파일러 가 할당을 해제 할 수d
있습니까?).
대답은 컴파일러 가 할당 해제 할 수 있고 주석이 나타내는 위치에 d
액세스 p[0]
하는 것이 정의되지 않은 동작이라는 것입니다 (프로그램은 내부 범위 외부의 내부 객체에 액세스 할 수 없음 ). C 표준의 관련 부분은 6.2.4p5입니다.
가변 길이 배열 유형이없는 객체 (자동 저장 기간이있는 객체) 의 경우 수명은 해당 블록의 실행이 어떤 식 으로든 끝날 때까지 연관된 블록으로 시작됩니다 . (동봉 된 블록을 입력하거나 함수를 호출하면 현재 블록의 실행이 일시 중단되지만 종료되지는 않습니다.) 블록이 재귀 적으로 입력되면 매번 객체의 새 인스턴스가 생성됩니다. 개체의 초기 값은 불확실합니다. 객체에 대해 초기화가 지정되면 블록 실행시 선언에 도달 할 때마다 수행됩니다. 그렇지 않으면 선언에 도달 할 때마다 값이 결정되지 않습니다.
귀하의 질문은 명확하게 답변 될만큼 명확하지 않습니다.
한편으로, 컴파일러는 일반적으로 중첩 된 블록 범위에 대해 로컬 메모리 할당 할당 해제를 수행하지 않습니다. 로컬 메모리는 일반적으로 기능 입력시 한 번만 할당되고 기능 종료시 해제됩니다.
반면, 로컬 객체의 수명이 끝나면 해당 객체가 점유 한 메모리를 나중에 다른 로컬 객체에 재사용 할 수 있습니다. 예를 들어이 코드에서
void foo()
{
{
int d[100];
}
{
double e[20];
}
}
두 어레이는 일반적으로 동일한 메모리 영역을 차지하므로 기능에 필요한 로컬 스토리지의 총량 은 두 어레이 중 가장 큰 어레이에 필요한 양이며 동시에 두 개의 어레이에 foo
필요한 것은 아닙니다.
후자 d
가 질문의 맥락에서 기능이 끝날 때까지 메모리 를 계속 차지 하는지 자격이 있는지 여부는 결정하는 것입니다.
구현에 따라 다릅니다. gcc 4.3.4의 기능을 테스트하는 간단한 프로그램을 작성했으며 함수 시작시 모든 스택 공간을 한 번에 할당합니다. -S 플래그를 사용하여 gcc가 생성 한 어셈블리를 검사 할 수 있습니다.
아니오, d []는 나머지 루틴 동안 스택에 없습니다 . 그러나 alloca ()는 다릅니다.
편집 : Kristopher Johnson (및 simon 및 Daniel)이 옳았 으며 초기 응답이 잘못되었습니다 . CYGWIN에서 gcc 4.3.4를 사용하면 코드는 다음과 같습니다.
void foo(int[]);
void bar(void);
void foobar(int);
void foobar(int flag) {
if (flag) {
int big[100000000];
foo(big);
}
bar();
}
제공합니다 :
_foobar:
pushl %ebp
movl %esp, %ebp
movl $400000008, %eax
call __alloca
cmpl $0, 8(%ebp)
je L2
leal -400000000(%ebp), %eax
movl %eax, (%esp)
call _foo
L2:
call _bar
leave
ret
살고 배우십시오! 그리고 빠른 테스트를 통해 AndreyT도 여러 할당에 대해 올바른 것으로 보입니다.
훨씬 나중에 추가 : 위의 테스트는 gcc 문서 가 옳지 않다는 것을 보여줍니다 . 수년 동안 그것은 (강조 추가) 말했다 :
" 배열 이름의 범위가 끝나 자마자 가변 길이 배열의 공간이 할당 해제 됩니다 ."
alloca
함수를 호출하지 않습니다 . cygwin gcc가 그렇게 할 것이라고 정말 놀랐습니다. 가변 길이 배열조차 아니기 때문에 IDK를 사용하는 이유는 무엇입니까?
변수 d
는 일반적으로 스택에서 튀어 나오지 않습니다. 중괄호는 스택 프레임을 나타내지 않습니다. 그렇지 않으면 다음과 같은 작업을 수행 할 수 없습니다.
char var = getch();
{
char next_var = var + 1;
use_variable(next_char);
}
중괄호로 인해 함수 호출과 같이 실제 스택 푸시 / 팝이 발생하는 경우 위의 코드는 중괄호 안의 코드가 중괄호 var
밖에있는 변수에 액세스 할 수 없기 때문에 컴파일되지 않습니다. 함수는 호출 함수의 변수에 직접 액세스 할 수 없습니다). 우리는 이것이 사실이 아니라는 것을 알고 있습니다.
중괄호는 단순히 범위 지정에 사용됩니다. 컴파일러는 둘러싸는 중괄호 외부에서 "내부"변수에 대한 액세스를 유효하지 않은 것으로 간주하고 해당 메모리를 다른 용도로 재사용 할 수 있습니다 (구현에 따라 다름). 그러나 둘러싸는 함수가 반환 될 때까지 스택에서 튀어 나오지 않을 수 있습니다.
업데이트 : C 스펙 의 내용은 다음과 같습니다 . 자동 보관 기간이있는 객체 (섹션 6.4.2) :
가변 길이 배열 유형이없는 객체의 경우 수명은 해당 블록의 실행이 어쨌든 종료 될 때까지 연관된 블록으로의 진입에서 연장됩니다.
동일한 섹션에서는 "평생"이라는 용어를 (강조 광산)으로 정의합니다.
객체 의 수명 은 스토리지가 예약되도록 보장 되는 동안 프로그램 실행의 일부입니다 . 개체가 존재하고 주소가 일정하며 수명 기간 동안 마지막으로 저장된 값을 유지합니다. 수명이 지난 개체를 참조하면 동작이 정의되지 않습니다.
여기서 핵심 단어는 물론 '보장'입니다. 내부 중괄호 세트의 범위를 벗어나면 어레이 수명이 끝납니다. 스토리지가 여전히 스토리지에 할당되었거나 할당되지 않았을 수 있지만 (컴파일러가 다른 공간을 재사용 할 수 있음) 어레이에 액세스하려고하면 정의되지 않은 동작이 발생하여 예측할 수없는 결과가 발생합니다.
C 스펙에는 스택 프레임에 대한 개념이 없습니다. 결과 프로그램의 작동 방식에 대해서만 이야기하고 구현 세부 사항을 컴파일러에 남겨 둡니다 (결국 하드웨어 스택이있는 CPU의 경우와 스택리스 CPU의 경우 구현이 상당히 다르게 보입니다). C 스펙에는 스택 프레임이 끝나거나 끝나지 않는 위치를 지정하는 것이 없습니다. 유일한 진짜 알 방법은 특정 컴파일러 / 플랫폼에서 코드를 컴파일하고 어셈블리 결과를 검토하는 것입니다. 컴파일러의 현재 최적화 옵션 세트도 이와 같은 역할을합니다.
당신이 배열하지 않은지 확인하려면 d
더 이상 코드가 실행되는 동안 메모리를 먹고, 당신도 별도의 기능 또는 명시 적으로 중괄호의 코드를 변환 할 수 있습니다 malloc
및 free
메모리 대신 자동 스토리지를 사용하여.
나는 그것이 범위를 벗어났다고 생각하지만 함수가 반환 될 때까지 스택에서 팝되지 않습니다. 따라서 함수가 완료 될 때까지 스택에서 메모리를 차지하지만 첫 번째 닫는 중괄호의 다운 스트림에는 액세스 할 수 없습니다.
표준에 대해서는 실제로 구현에 따라 다름을 나타내는 많은 정보가 이미 제공되어 있습니다 .
따라서 하나의 실험이 흥미로울 수 있습니다. 다음 코드를 시도하면
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
printf("%p\n", (void*) x);
}
{
int b;
y = &b;
printf("%p\n", (void*) y);
}
}
gcc를 사용하여 동일한 주소의 두 배를 얻습니다 : Coliro
그러나 다음 코드를 시도하면
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
}
{
int b;
y = &b;
}
printf("%p\n", (void*) x);
printf("%p\n", (void*) y);
}
우리는 여기에 두 개의 서로 다른 주소를 가져 GCC를 사용 : Coliro를
그래서, 당신은 정말로 무슨 일이 일어나고 있는지 확신 할 수 없습니다.