🧩 문제 1.
short sArr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pI = (int*)sArr;
int iData = ((short*)(pI + 2));
printf("1번 문제 정답 : %d\n", iData);
🖨️ 출력 결과
1번 문제 정답 : 5
🔍 해설
- short sArr[10]은 2바이트 정수 10개로 구성된 배열입니다.
- int* pI = (int*)sArr;
→ short*을 int*로 강제로 캐스팅하였기에, 포인터의 연산 단위가 4바이트로 바뀝니다. - pI + 2는 4바이트 * 2 → 8바이트(=4개의 short) 만큼 증가합니다.
즉, sArr[4]와 sArr[5]를 가리키게 됩니다. 즉 {1,2,3,4,👉5,6,7,8,9,10} 중에서 5,6 위치. - 이후 (short*)(pI + 2)로 다시 short*로 바꿔서 역참조합니다.
→ short* 관점에서 보면 5가 위치한 주소가 되고, iData에는 5가 들어갑니다.
✅ 정답: 5
🧩 문제 2.
char cArr[2] = { 1, 1 };
short* pS = (short*) cArr;
iData = *pS;
printf("2번 문제 정답 : %d\n", iData);
🖨️ 출력 결과
2번 문제 정답 : 257
🔍 해설
- char cArr[2] = {1, 1}; → 각각 1바이트씩 1이 들어 있음.
- short* pS = (short*)cArr;
→ char[2]을 short*로 바꾸었으니, 2바이트 단위로 읽게 됩니다. - iData = *pS;
→ cArr[0] = 0x01, cArr[1] = 0x01 → 리틀 엔디안에서는 0x0101
※ 리틀 엔디안(Little Endian)
📦 리틀 엔디안 vs 빅 엔디안
🔸 엔디안이란?
엔디안(Endianness)은 2바이트 이상인 데이터(예: int, short 등)를 메모리에 어떤 순서로 저장할 것인지를 결정하는 방식입니다.
쉽게 말해, 숫자의 바이트 순서를 메모리에 어떻게 나열할까? 라는 문제입니다.
예를 들어, 0x1234라는 2바이트 데이터를 저장할 때
- 앞에서부터 저장할까?
- 뒤에서부터 저장할까?
이 선택에 따라 리틀 엔디안 또는 빅 엔디안이 됩니다.
🔹 리틀 엔디안 (Little Endian)
"작은 값(LSB)을 먼저 저장해요!"
주소 | 저장 값 |
0x00 | 0x34 |
0x01 | 0x12 |
📌 즉, 역순으로 저장됩니다.
🔹 빅 엔디안 (Big Endian)
"큰 값(MSB)을 먼저 저장해요!"
주소 | 저장 값 |
0x00 | 0x12 |
0x01 | 0x34 |
📌 우리가 일반적으로 숫자를 읽는 순서와 같아요.
🎯문제2 로 다시 보기
char cArr[2] = { 1, 1 }; // 10진수로 [1, 1]
short* pS = (short*) cArr;
int iData = *pS;
🔍 메모리에 저장된 모습 (리틀 엔디안 기준):
주소 | 값 (16진수) |
cArr[0] | 0x01 |
cArr[1] | 0x01 |
📌 short* 포인터로 읽으면 두 바이트를 한꺼번에 읽기 때문에,
👉 0x01(하위 바이트) + 0x01(상위 바이트) → 0x0101
💡 결과적으로 0x0101 = 257 (10진수)가 됩니다!
🧪 만약 빅 엔디안이었다면?
주소 | 값 |
cArr[0] | 0x01 (상위 바이트) |
cArr[1] | 0x01 (하위 바이트) |
➡️ 역시 0x0101이지만,
만약 값이 { 0x01, 0x02 }였다면?
- 🐥 리틀 엔디안: 0x0201 → 513
- 🦁 빅 엔디안: 0x0102 → 258
😨 이처럼 동일한 데이터라도 해석 결과가 달라질 수 있기 때문에,
💾 네트워크, 바이너리 파일, 포인터 캐스팅 등에서는 엔디안 정리가 꼭 필요합니다!
🧠 한 줄 요약
📝 리틀 엔디안은 하위 바이트(작은 값)가 앞에 오는 방식입니다.
대부분의 시스템 (특히 x86 기반)은 리틀 엔디안을 사용하고 있습니다.
🧠 2진수로 보면:
00000001 (LSB)
00000001 (MSB)
=> 00000001 00000001 = 257 (10진수)
✅ 정답: 257
💡 의도적으로 1바이트짜리 배열을 2바이트 단위로 읽게끔 변환했기 때문에, 이처럼 예상치 못한 값이 나올 수 있습니다.
🧩 문제 3.
void Test1(int a) {
a = 500;
}
void Test2(int* a) {
*a = 500;
}
int main() {
int a = 100;
Test1(a);
printf("Test1 : %d\n", a);
Test2(&a);
printf("Test2 : %d\n", a);
return 0;
}
🖨️ 출력 결과
Test1 : 100
Test2 : 500
🔍 해설
- Test1(int a)
→ 값에 의한 전달(Call by Value)
→ main 함수의 변수 a 값을 복사해서 사용하므로, 원본에는 영향이 없습니다. - Test2(int* a)
→ 주소에 의한 전달(Call by Reference)
→ 실제 변수 a의 주소를 전달받았기 때문에, 그 주소에 있는 값을 직접 바꿀 수 있습니다.
✅ Test1은 값이 안 바뀌고, Test2는 실제 변수 a가 변경됨.
🧠 한 줄 요약
- 🔧 값을 바꾸고 싶다면 반드시 포인터를 써야 한다!
- 📌 scanf() 함수도 마찬가지로 주소를 요구하는 이유가 바로 여기에 있음.
📝 포인터 개념 확장 — scanf 계열 함수 요약
구분 | 기본 함수 | 보안 강화 함수 | locale 지원 함수 | 사용 예시 |
콘솔 입력 | scanf | scanf_s | _scanf_s_l | scanf("%d", &num); |
파일 입력 | fscanf | fscanf_s | ❌ (locale 지원 없음) | fscanf(fp, "%d", &num); |
문자열 입력 | sscanf | sscanf_s | _sscanf_s_l | sscanf(str, "%d", &num); |
길이 제한 | _snscanf | _snscanf_s | _snscanf_s_l | _snscanf_s(str, 10, "%s", buf, size); |
⚠️ _snscanf_s, _sscanf_s_l 등은 Visual Studio 전용이므로 GCC, Clang에서는 사용 불가입니다.
🧠 마무리 한 줄 요약
포인터는 메모리를 직접 다루는 칼이다.
값을 읽고 바꾸는 모든 행위는 주소에서 시작되며, 형(type) 변환에 따라 해석 방식이 전혀 달라질 수 있으므로 항상 메모리 크기와 정렬을 고려하자.
💡 실전 Tip
- 포인터가 가리키는 대상의 크기 단위가 연산의 기본이 됩니다.
- 무조건 외우기보다, "왜 이렇게 나올까?"를 메모리 입장에서 상상해보는 것이 중요합니다.
- 시각화 도구(VS Memory Window, 그림 등)를 활용해보면 더 빠르게 체득됩니다.
'C++ > 유튜브 어소트락 게임아카데미 C++무료강의' 카테고리의 다른 글
11. void 포인터 (void*) (0) | 2025.04.19 |
---|---|
10. 포인터와 const (0) | 2025.04.19 |
8. 포인터 (0) | 2025.04.15 |
7. 지역변수, 전역변수 (0) | 2025.04.02 |
6. 구조체(Structure) (0) | 2025.03.30 |