객체 수명
객체 수명 (lifetime)
C에서 객체는 프로그램 실행 중 일정한 구간 동안만 존재해요. 그 객체가 존재하고 유효한 그 구간을 수명(lifetime)이라고 해요. 수명이 끝난 객체에 접근하는 건 정의되지 않은 동작이라, 저장 기간(storage duration)과의 관계를 이해하는 게 안전한 코드의 기본이에요.
출처: cppreference
본문
C의 모든 객체는 프로그램 실행의 특정 구간 동안 존재하고, 일정한 주소를 가지며, 마지막으로 저장된 값을 유지해요(값이 불확정인 경우는 제외). 그리고 VLA의 경우 그 크기들도 유지해요(C99부터). 이 구간이 바로 그 객체의 수명 이에요.
자동(automatic), 정적(static), 스레드(thread) 저장 기간으로 선언된 객체의 경우, 수명은 저장 기간과 같아요(비-VLA와 VLA 자동 저장 기간의 차이에 주의).
할당된(allocated) 저장 기간을 가진 객체의 수명은 할당 함수가 반환할 때(재할당 함수 realloc의 반환 포함) 시작되고, realloc이나 해제 함수가 호출될 때 끝나요. 할당된 객체는 선언된 타입이 없기 때문에, 이 객체에 접근하는 데 처음 사용된 lvalue 표현식의 타입이 그 객체의 **유효 타입(effective type)**이 된다는 점도 참고할게요.
객체를 수명 밖에서 접근하는 것은 정의되지 않은 동작이에요.
int* foo(void) {
int a = 17; // a has automatic storage duration
return &a;
} // lifetime of a ends
int main(void) {
int* p = foo(); // p points to an object past lifetime ("dangling pointer")
int n = *p; // undefined behavior
}
수명이 끝난 객체(또는 그 객체의 바로 다음 위치)를 가리키는 포인터는 불확정 값을 가져요.
임시 수명
배열 멤버를 가진(직접이든 중첩된 구조체·공용체 멤버를 통해서든) 구조체·공용체 객체가 비-lvalue(non-lvalue) 표현식으로 지정되면, 그 객체는 임시 수명 을 가져요. 임시 수명은 그런 객체를 참조하는 표현식이 평가될 때 시작되고, 다음 순서점(sequence point)(C11 이전) 또는 그것을 포함하는 전체 표현식·전체 선언자가 끝날 때(C11부터) 끝나요.
임시 수명을 가진 객체를 수정하려는 시도는 정의되지 않은 동작이에요.
struct T { double a[4]; };
struct T f(void) { return (struct T){3.15}; }
double g1(double* x) { return *x; }
void g2(double* x) { *x = 1.0; }
int main(void)
{
double d = g1(f().a); // C99: UB access to a[0] in g1 whose lifetime ended
// at the sequence point at the start of g1
// C11: OK, d is 3.15
g2(f().a); // C99: UB modification of a[0] whose lifetime ended at the sequence point
// C11: UB attempt to modify a temporary object
}
더 알아보기
- 수명과 밀접하게 연결된 저장 기간(storage duration) 규칙은
storage duration문서를 참고해요. - 수명이 끝난 객체로 생기는 댕글링 포인터 같은 함정을 피하려면 객체·정렬 관련 문서도 함께 읽어 보면 좋아요.