C언어를 공부하다 보면 많은 사람이 포인터에서 어려움을 느낀다.
문법 자체가 낯설고, `*`, `&` 같은 기호가 계속 등장하기 때문이다.
그래서 “포인터는 무조건 어렵다”라고 생각하는 경우도 많다.
하지만 포인터는 복잡한 개념이 아니다.
핵심만 정리하면 값 자체가 아니라, 값이 저장된 위치를 다루는 방법이다.
C언어가 하드웨어 제어, 임베디드 시스템, 운영체제, 드라이버 개발에 많이 사용되는 이유도 바로 이 포인터 덕분이다.
메모리를 직접 다룰 수 있기 때문이다.
이번 글에서는 포인터의 기본 개념, 왜 필요한지, 그리고 실무에서는 어떻게 사용하는지까지 이해하기 쉽게 정리한다.
포인터란 무엇인가?
포인터는 변수의 메모리 주소를 저장하는 변수다.
일반 변수는 데이터를 직접 저장한다.
uint8 num = 10U;
위 코드는 `num`이라는 변수 안에 숫자 10을 저장한다.
반면 포인터 변수는 숫자 10을 저장하는 것이 아니라,
10이 저장되어 있는 메모리 위치(주소) 를 저장한다.
즉, 역할이 다르다.
* 일반 변수 → 실제 값 저장
* 포인터 변수 → 값이 있는 주소 저장
이 차이를 이해하는 것이 포인터의 시작이다.
메모리 주소는 무엇인가?
컴퓨터 메모리는 데이터를 보관하는 공간이다.
그리고 메모리의 각 위치에는 고유한 번호가 있다. 이 번호를 주소(Address)라고 한다.
예를 들어 어떤 변수가 메모리 1000번지에 저장되어 있다고 가정해보자.
주소 1000 : 값 10
그렇다면 변수 'num'은 값 10을 가지고 있고,
그 값은 메모리 주소 1000에 저장된 것이다.
포인터는 바로 이 1000이라는 위치 정보를 저장한다.
포인터 기본 문법
uint8 num = 10U;
uint8 *ptr = #
이 코드를 하나씩 해석해보자.
uint8 num = 10U;
부호없는 정수형 변수 'num'을 만들고 값 10을 저장한다.
&num
'&' 연산자는 변수의 주소를 가져온다.
즉, '&num' 은 "num이 저장된 메모리 주소"를 의미한다.
uint8 *ptr
'ptr' 은 부호없는 정수형 데이터를 가리키는 포인터 변수라는 뜻이다.
uint8 *ptr = #
결과적으로 'ptr' 에는 'num' 의 주소가 저장된다.
즉, num 과 ptr 은 아래와 같다.
num = 10
ptr = num의 주소
*ptr은 왜 사용하는가?
포인터 변수에 저장된 것은 주소다.
하지만 우리는 주소 자체보다, 그 주소 안에 들어 있는 실제 값이 필요한 경우가 많다.
이때 사용하는 것이 * 연산자다.
*ptr 은 ptr이 가리키는 주소에 있는 값을 가져온다는 뜻이다.
만약 ptr이 num의 주소를 가지고 있다면:
*ptr = num의 값 = 10
즉, 출력 결과는 10이다.
포인터로 원본 값을 수정할 수 있는 이유
포인터의 가장 강력한 특징은 원본 데이터를 직접 수정할 수 있다는 점이다.
uint8 num = 10U;
uint8 *ptr = #
*ptr = 20U;
이 코드를 실행하면 'num' 값이 20으로 변경된다.
왜냐하면 ptr은 num의 주소를 알고 있고,
그 주소에 직접 접근해서 값을 바꾸기 때문이다.
결과:
num = 20
즉, 포인터는 단순히 주소만 저장하는 도구가 아니라
실제 데이터를 간접적으로 제어하는 도구다.
왜 포인터가 필요한가?
1. 함수에서 원본 값을 변경할 수 있다
C언어 함수는 기본적으로 값을 복사해서 전달한다.
그래서 일반 변수만 넘기면 함수 내부에서 값을 바꿔도 원본은 변하지 않는다.
void change(uint8 num)
{
num = 50U;
}
하지만 포인터를 사용하면 원본 값을 바꿀 수 있다.
void change(uint8 *p)
{
*p = 50U;
}
uint8 main(void)
{
uint8 num = 10U;
change(&num);
return 0U;
}
실행 후 'num' 값은 50이 된다.
실무에서는 설정값 변경, 상태값 업데이트, 구조체 데이터 수정 등에 매우 자주 사용된다.
2. 배열 처리에 효율적이다
배열 이름은 첫 번째 요소의 주소로 동작한다.
int arr[3] = {1, 2, 3};
int *p = arr;
즉, 'p' 는 'arr[0]' 을 가리킨다.
printf("%d", *p); // 1
printf("%d", *(p+1)); // 2
printf("%d", *(p+2)); // 3
배열 순회, 버퍼 처리, 데이터 파싱에서 자주 사용된다.
AUTOSAR 실무 예제 1. 포인터로 여러 데이터 전달하기
차량 소프트웨어에서는 하나의 함수가 여러 상태값을 동시에 전달해야 하는 경우가 많다.
예를 들어 모터 상태를 읽을 때 전류값, 온도값, 에러 상태를 한 번에 가져와야 할 수 있다.
이때 포인터를 사용하면 하나의 함수에서 여러 값을 전달할 수 있다.
#include <Std_Types.h>
void Motor_ReadStatus(uint16 *current, uint16 *temperature, uint8 *error)
{
*current = 15U;
*temperature = 42U;
*error = 0U;
}
사용:
uint16 MotorCurrent = 0U;
uint16 MotorTemp = 0U;
uint8 MotorError = 0U;
Motor_ReadStatus(&MotorCurrent, &MotorTemp, &MotorError);
실행 후:
* MotorCurrent = 15
* MotorTemp = 42
* MotorError = 0
반환값 하나로는 부족한 데이터를 동시에 전달 가능하다.
센서 모듈 / 진단 모듈 / 제어 모듈에서 자주 사용하는 구조이다.
AUTOSAR 실무 예제 2. RTE API와 포인터
AUTOSAR에서는 'Rte_Read', 'Rte_Call' 같은 RTE API에서 출력값을 포인터로 전달하는 경우가 많다.
예:
Std_ReturnType RetVal;
uint8 VehicleSpeed = 0U;
RetVal = Rte_Read_PpVehicleSpeed_Value(&VehicleSpeed);
설명:
- 'VehicleSpeed' 변수의 주소를 전달
- RTE 내부에서 해당 주소에 값을 기록
- 호출 후 VehicleSpeed에 최신 데이터 저장
왜 이렇게 설계했을까?
RTE API는 함수의 반환값(return)을 성공/실패 상태(Std_ReturnType) 전달에 사용하고,
실제 데이터는 출력 파라미터(포인터)를 통해 전달하도록 설계되어 있다.
따라서 호출자는 함수 실행 결과와 데이터 값을 동시에 확인할 수 있다.
이 구조는 AUTOSAR에서 매우 흔하다.
정리
포인터는 메모리 주소를 저장하는 변수다.
& 는 주소를 가져오는 연산자다.
* 는 주소에 있는 실제 값을 다룬다.
함수에서 원본 데이터 수정이 가능하다.
배열, 동적 메모리, 임베디드 제어에 필수다.
포인터는 값을 직접 다루는 것이 아니라, 값이 있는 위치를 다루는 기술이다.
'C 언어 > 실무' 카테고리의 다른 글
| C언어 비트 연산자 쉽게 설명하기 (임베디드 실무 관점에서 제대로 이해하기) (0) | 2026.04.14 |
|---|---|
| C언어 extern 변수 사용법 (파일 간 변수 공유 방법) (0) | 2026.04.13 |
| C언어 배열과 포인터 차이 (헷갈리는 이유부터 핵심 차이까지) (0) | 2026.04.12 |
| C언어 volatile은 언제 써야 하는가? (0) | 2026.04.11 |
| C언어 static 변수는 왜 쓰는가? (실무에서 안 쓰면 생기는 문제) (0) | 2026.04.11 |