본문 바로가기
C 언어/실무

C언어 포인터 쉽게 설명하기 (개념부터 실무 예제까지 한 번에)

by Autosar 2026. 4. 11.
반응형

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에서 매우 흔하다.

 

정리

 

포인터는 메모리 주소를 저장하는 변수다.
& 는 주소를 가져오는 연산자다.
* 는 주소에 있는 실제 값을 다룬다.
함수에서 원본 데이터 수정이 가능하다.
배열, 동적 메모리, 임베디드 제어에 필수다.

 

포인터는 값을 직접 다루는 것이 아니라, 값이 있는 위치를 다루는 기술이다.

반응형