논리 연산자의 쇼트 서킷(Short Circuit) 현상
C 언어에서 &&와 || 연산자는 쇼트 서킷 평가(Short-circuit evaluation) 방식을 사용합니다. 이는 첫 번째 피연산자만으로 전체 결과가 확정되면 두 번째 피연산자를 평가하지 않는 특성입니다.
다음 코드는 흔히 발생하는 오류 사례입니다:
// 문제가 있는 코드: b가 0일 때 a % b 연산에서 SIGSEGV 발생
if (temp == 3 && (a % b != 0 || b == 0)) {
printf("go");
}
위 코드에서 temp == 3이 참일 경우 (a % b != 0 || b == 0)이 평가됩니다. 그런데 || 연산자는 왼쪽부터 평가하므로 a % b != 0을 먼저 검사합니다. 만약 b가 0이라면 0으로 나누기 연산이 발생하여 SIGSEGV 신호가 발생합니다.
올바른 코드는 다음과 같이 조건 순서를 변경해야 합니다:
// 수정된 코드: b가 0인 경우를 먼저 검사
if (temp == 3 && (b == 0 || a % b != 0)) {
printf("go");
}
이제 ||의 왼쪽에서 b == 0을 먼저 검사하므로, b가 0이라면 쇼트 서킷에 의해 a % b != 0은 평가되지 않습니다.
구조체 메모리 레이아웃 분석
C 구조체는 내부 변수들이 선언 순서대로 연속적인 메모리 공간에 배치되며, 구조체 자체의 메타데이터(크기 등)를 포함하지 않습니다. 다음 코드를 통해 이를 확인할 수 있습니다:
#include <stdio.h>
typedef struct {
int a;
int b;
int c;
int d;
} TD;
int main(void) {
TD data;
TD *dataPtr = &data;
dataPtr->a = 0xA00AA11A;
dataPtr->b = 0xB00BB22B;
dataPtr->c = 0xC00CC33C;
dataPtr->d = 0xD00DD44D;
printf("int 크기: %d 바이트\n", (int)sizeof(int));
printf("포인터 크기: %d 바이트\n", (int)sizeof(int *));
// 구조체 전체를 8바이트 단위로 읽기
printf("시작 주소 데이터: %llX\n", *(unsigned long long *)dataPtr);
// 포인터 연산으로 각 멤버 접근
int *intPtr = (int *)dataPtr;
printf("멤버 a: %X\n", *(intPtr + 0));
printf("멤버 b: %X\n", *(intPtr + 1));
printf("멤버 c: %X\n", *(intPtr + 2));
printf("멤버 d: %X\n", *(intPtr + 3));
// 각 멤버의 주소 출력
printf("a 주소: %p\n", &data.a);
printf("b 주소: %p\n", &data.b);
printf("c 주소: %p\n", &data.c);
printf("d 주소: %p\n", &data.d);
return 0;
}
실행 결과:
int 크기: 4 바이트
포인터 크기: 8 바이트
시작 주소 데이터: B00BB22BA00AA11A
멤버 a: A00AA11A
멤버 b: B00BB22B
멤버 c: C00CC33C
멤버 d: D00DD44D
a 주소: 0x7ffe7b2715b0
b 주소: 0x7ffe7b2715b4
c 주소: 0x7ffe7b2715b8
d 주소: 0x7ffe7b2715bc
주요 발견점
- 구조체의 시작 주소와 첫 번째 멤버(
a)의 주소가 동일합니다. - 각
int멤버는 4바이트 간격으로 연속 배치됩니다. - 구조체는 단순히 멤버 변수들의 연속된 메모리 블록으로, 구조체 자체의 크기 정보를 저장하지 않습니다.
- 포인터 캐스팅을 통해 구조체를 정수 배열처럼 접근할 수 있습니다.
참고: 구조체 메모리 정렬(Alignment)에 대한 상세 내용은 "C Primer Plus" 서적을 참조하세요. 실제로는 컴파일러가 성능 최적화를 위해 패딩 바이트를 추가할 수 있습니다.