Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C 언어에서 엄격한 앨리어싱(Strict Aliasing)이 왜 필요한가?

C 프로그래밍에서 엄격한 앨리어싱(strict aliasing) 규칙은 왜 지켜야 할까요? 이를 이해하기 위해, 먼저 간단한 예제 코드를 살펴보고 그 출력 결과를 분석해 보겠습니다.

예제 코드

#include<stdio.h>
int temp = 5;
int* var = &temp;
int my_function(double* var) {
    temp = 1;
    *var = 5.10; // 이 연산이 temp의 값을 변경하게 됩니다
    return (temp);
}
main() {
    printf("%d", my_function((double*)&temp));
}

출력 결과

1717986918

결과 분석

코드의 의도대로라면 my_function을 호출하면 1이 반환되어야 합니다. 함수 내부에서 temp = 1;을 실행한 직후 return (temp);로 값을 돌려주기 때문입니다.

그러나 실제 출력 결과는 1717986918이라는 전혀 다른 값입니다. 원인은 my_function((double*)&temp)처럼 int형 변수의 주소를 double* 포인터로 강제 형변환하여 접근했기 때문입니다. 이는 C 표준의 엄격한 앨리어싱 규칙을 위반하는 행위이며, 컴파일러는 서로 다른 타입의 객체가 같은 메모리를 공유하지 않는다고 가정하고 최적화를 수행합니다. 그 결과 프로그램의 동작은 정의되지 않은 동작(undefined behavior)이 되고, 예상과 다른 값이 반환됩니다.

해결 방법

이 문제를 해결하는 가장 확실한 방법은 엄격한 앨리어싱 규칙을 준수하는 것입니다. 즉, 한 타입으로 생성된 객체에 다른 타입의 포인터로 접근하지 않아야 합니다. 타입 변환이 꼭 필요하다면 memcpy나 공용체(union)를 활용하는 것이 안전합니다.

또한 컴파일 시 -fno-strict-aliasing 옵션을 사용하여 엄격한 앨리어싱 기반 최적화를 비활성화할 수도 있지만, 이는 성능 저하를 감수하는 임시방편일 뿐 근본적인 해결책은 아닙니다.

참고로 restrict 한정자(qualifier) 키워드도 관련된 개념입니다. 이 키워드는 해당 포인터가 가리키는 객체가 다른 포인터와 앨리어싱(별칭)되지 않음을 컴파일러에게 약속하는 것입니다. 만약 이 약속을 어기고 실제로 별칭이 존재한다면, 최적화된 코드에서 예기치 못한 오류가 발생할 수 있습니다.