🌟 구조1, & 주소 연산자
포인터는 메모리 주소를 저장하는 변수로, C언어에서 가장 중요하면서도 어려운 개념 중 하나.
① (&) 연산자 개념
- 정식 명칭: Address-of 연산자 (주소 추출 연산자)
- 변수가 저장된 메모리의 주소를 반환
- 사용 방법: &변수명
② 메모리 주소란?
- 컴퓨터의 메모리는 수많은 작은 저장공간들이 일렬로 배열된 구조이다. 각 저장공간마다 고유의 번호가 있는데, 이를 메모리 주소라 부른다. 마치 아파트의 각 호수처럼 각 메모리 공간을 구분하는 번호다.
③ 코드 상세 분석
#include <stdio.h>
void main()
{
int iNum = 90; // 정수형 변수 iNum을 선언하고 90으로 초기화
// %d: 정수를 10진수로 출력하는 형식 지정자
// iNum: 변수의 값 (90)을 출력
printf("iNum 주소: %d\n", iNum);
// &iNum: iNum 변수가 저장된 메모리 주소를 출력
printf("iNum 주소값: %d\n", &iNum);
}
㉮ 변수 선언: int iNum = 90;
- 메모리에 4바이트 공간을 할당받는다.
- 그 공간에 90이라는 값을 저장한다,
- 그 공간에는 고유한 주소가 부여된다.
㉯ printf
- 첫 번째 printf로 90이라는 값을 가져온다.
- 두 번째 printf로 iNum변수의 메모리 주소를 가져온다.

④ 메모리 다이어그램 해석
㉮ 메모리 구조 분석
- 주소 영역: 0x001FF2 등의 16진수 주소값들
- 메모리 내용: 각 메모리 위치에 저장된 값들 (0,1 등)
- 변수 위치: iNum = 90이 특정 메모리 주소에 저장됨.
㉯ 16진수 주소 표기법
- 0x: 16진수임을 나타내는 접두사
- 001FF2: 실제 메모리 주소
- 16진수를 사용하는 이유: 메모리 주소는 보통 매우 큰 숫자이므로 16진수로 표현하면 더 간결하기 때문에
⑤ 포인터 학습의 중요성
㉮ 왜 포인터를 배워야 하는가?
- 메모리 직접 제어: 효율적인 메모리 사용 가능
- 함수 간 데이터 전달: 큰 데이터를 복사하지 않고 주소만 전달
- 동적 데이터 할당: 프로그램 실행 중 필요한 만큼 메모리 할당
🌟 구조2, * 참조 연산자
앞서 & 연산자가 주소를 얻는 역할이었다면, * 연산자는 그 주소에 저장된 값에 접근하는 역할
① 애스터 리스크 연산자의 두 가지 역할
㉮ 포인터 변수 선언
int* p_iNum; // 정수형 포인터 변수 선언
- 의미: 정수를 가르키는 포인터 변수를 선언
- 저장 내용: 메모리 주소값
- 크기: 포인터는 항상 8바이트 또는 4바이트
㉯ 간접 참조
*p_iNum // 포인터가 가리키는 위치의 값에 접근
- 의미: 포인터가 가르키는 메모리 위치에 저장된 실제 값
- 용도: 포인터를 통해 원본 데이터에 접근하거나 수정
② 코드 상쇄 분석
#include <stdio.h>
int main(void)
{
int iNum = 90; // 일반 정수 변수 선언 및 초기화
// 변수의 값과 주소 출력
printf("iNum 변수의 정수값: %d\n", iNum); // 90 출력
printf("iNum 변수의 주소값: %p\n", &iNum); // 메모리 주소 출력
// 포인터 변수 선언 및 초기화
int* p_iNum = &iNum; // iNum의 주소를 포인터에 저장
// 포인터를 통한 값 접근
printf("p_iNum 포인터 변수 값: %p\n", p_iNum); // 저장된 주소 출력
printf("p_iNum 포인터 역참조 값: %d\n", *p_iNum); // 가리키는 곳의 값(90) 출력
return 0;
}
㉮ 일반 변수 선언
- 메모리 할당: 4바이트 공간 할당
- 값 저장: 90이라는 정수값 저장
- 주소: 시스템이 자동으로 메모리 주소 할당 (예: 0x0061FF17)
㉯ 포인터 변수선언
- 포인터 선언: int* 는 정수를 가르키는 포인터를 의미
- 초기화: &iNum으로 iNum의 주소를 포인터에 저장
- 명명 규칙: p_는 포인터임을 나타내는 일반적인 접두사
㉰ 출력문 분석
- p_iNum: 포인터 자체의 값 (저장하고 있는 주소값)
- 출력 결과: 0x0061FF17 (iNum의 주소)
- *p_iNum: 포인터가 가리키는 위치의 실제 값
- 출력 결과: 90 (iNum에 저장된 값)
③ 메모리 다이어그램 해석

㉮ iNum 변수의 메모리 위치
- 주소: 0x0061FF17
- 저장값: 90 (10진수)
- 바이너리 표현: 이진법으로 변환된 90의 값이 메모리에 저장
㉯ p_iNum 포인터의 메모리 위치
- 주소: 0x0061FF28 (포인터 자체의 주소)
- 저장값: 0x0061FF17 (iNum의 주소를 저장)
- 크기: 포인터는 주소를 저장하므로 시스템에 따라 4바이트 또는 8바이트
// 메모리 관계도
[일반 변수] [포인터 변수]
주소: 0x0061FF17 주소: 0x0061FF28
┌─────────────┐ ┌─────────────────┐
│ iNum │ │ p_iNum │
│ 90 │ ◄─────────── │ 0x0061FF17 │
└─────────────┘ └─────────────────┘
값 주소값
④ 핵심 원리
- 주소 저장: 포인터는 다른 변수의 메모리 주소를 저장하는 변수
- 간접 접근: * 연산자로 포인터가 가리키는 실제 값에 접근
- 메모리 효율성: 큰 데이터를 복사하지 않고 주소만 전달하여 효율성 증대
🌟 구조3, 지역변수와 포인터 전달 방식
int func1(int num) // 매개변수: 일반 정수형 변수
{
num = 20; // 지역변수 num에 20을 대입
return 0; // 함수 종료
}
int func2(int* num) // 매개변수: 정수형 포인터
{
*num = 20; // 포인터가 가리키는 위치의 값을 20으로 변경
return 0; // 함수 종료
}
int main(void)
{
int num = 0; // num 변수를 0으로 초기화
func1(num); // 값 전달
printf("%d", num); // 결과: 0 출력
func2(&num); // 주소 전달
printf("%d", num); // 결과: 20 출력
return 0;
}
① 함수별 상세 분석
㉮ func1 함수 - 값에 의한 전달 (Call by Value)
ⓐ 동작 원리
- 매개변수 복사: main 함수의 num 값(0)이 func1의 지역변수 num에 복사
- 독립적인 메모리: func1의 num은 main의 num과 완전히 별개인 메모리 공간을 사용
- 값 변경: func1 내에서 num = 20으로 변경해도 main의 num에는 영향 없음
- 함수 종료: func1이 끝나면 지역변수는 메모리에서 제거됨
// 메모리 구조
[main 함수의 num] [func1 함수의 num]
주소: 0x1000 주소: 0x2000
┌─────────────┐ ┌─────────────┐
│ 0 │ │ 20 │ ← 여기서만 변경됨
└─────────────┘ └─────────────┘
원본 (변경안됨) 복사본 (변경됨)
㉯ func2 함수 - 참조에 의한 전달 (Call by Reference)
ⓐ 동작 원리
- 주소 전달: main 함수의 num 변수의 주소가 func2로 전달
- 같은 메모리 참조: func2의 포인터는 main의 num과 동일한 메모리 위치를 가리킴
- 원본 변경: *num = 20으로 원본 데이터를 직접 수정
- 영구적 변경: 함수가 끝나도 main의 num 값은 변경된 상태로 유지
// 메모리 구조
[main 함수의 num] [func2 함수의 포인터]
주소: 0x1000 주소: 0x3000
┌─────────────┐ ┌─────────────┐
│ 20 │ ◄─────── │ 0x1000 │
└─────────────┘ └─────────────┘
원본 (변경됨) 주소값 저장
② main 함수 실행 과정 분석
// 초기 상태
int num = 0; // num 변수를 0으로 초기화
// 첫 번째 함수 호출
func1(num); // 값 전달
printf("%d", num); // 결과: 0 출력
㉮ 실행 과정
- func2(&num) 호출 시 num의 주소가 전달
- func2 내부에서 포인터를 통해 원본 값을 20으로 변경
- 함수 종료 후에도 main의 num은 20으로 변경된 상태
- printf로 20이 출력됨
③ 실행 결과 예상
- 첫 번째 출력 (0): func1은 복사본만 변경했으므로 원본은 그대로
- 두 번째 출력 (20): func2는 원본을 직접 변경했으므로 값이 바뀜
④ 핵심 개념 비교표
구분 | 값에 의한 전달 (Call by Value) | 참조에 의한 전달 (Call by Reference) |
매개변수 타입 | int num | int* num |
전달 내용 | 변수의 값 | 변수의 주소 |
매모리 사용 | 새로운 메모리 할당 | 기존 메모리 공간 공유 |
원본 변경 | 불가능 | 가능 |
함수 호출 | func(variable) | func(&variable) |
값 접근/변경 | 직접 접근 num = 20 | 간접 접근 *num = 20 |
메모리 효율성 | 낮음 (복사 필요) | 높음 (주소만 전) |
⑤ 지역변수의 특성
정의: 함수 내부에서 선언된 변수
생명주기: 함수가 시작될 때 생성, 함수가 끝날 때 소멸
유효범위: 선언된 함수 내부에서만 접근 가능
//지역변수의 메모리 할당
int func1(int num) // num은 func1의 지역변수
{
num = 20; // 이 num은 main의 num과 다른 변수
return 0;
} // 여기서 지역변수 num은 메모리에서 제거됨
⑥ 함수 설계 시 고려사항
㉮ 값 전달을 선택해야 하는 경우
- 원본 보호: 함수 내에서 실수로 원본을 변경하는 것을 방지
- 단순 계산: 입력값을 이용한 계산 결과만 필요한 경우
- 안전성: 데이터 무결성이 중요한 경우
㉯ 포인터 전달을 선택해야 하는 경우
- 원본 수정: 함수에서 원본 데이터를 변경해야 하는 경우
- 메모리 효율성: 큰 구조체나 배열을 전달할 때
- 다중 반환값: 함수에서 여러 값을 반환해야 하는 경우
- 동적 메모리: malloc으로 할당한 메모리를 다룰 때
⑦ 주의사항 및 일반적인 실수
int func(int* num) {
if (num == NULL) { // NULL 포인터 체크 필수
return -1; // 오류 처리
}
*num = 20;
return 0;
}
- NULL 포인터 역참조: 포인터가 NULL인지 확인하지 않고 사용
- 타입 불일치: 포인터 타입과 실제 데이터 타입이 맞지 않는 경우
- 지역변수 주소 반환: 함수 내 지역변수의 주소를 반환하는 실수
'C > Concept' 카테고리의 다른 글
C언어 형변환 개념정리 (0) | 2025.06.17 |
---|---|
C언어 포인터 개념정리_1 (3) | 2025.06.13 |
C언어 소스코드 개념정리 (2) | 2025.06.11 |