본문 바로가기
C 언어/기초 문법

[13장] C언어 포인터(Pointer) 기초 — 메모리 주소를 다루는 핵심 문법

by Autosar 2026. 5. 17.
반응형

C 언어를 공부하다 보면 가장 많이 듣게 되는 말 중 하나가 있다.

“포인터를 이해해야 C 언어를 제대로 이해할 수 있다.”

 

실제로 포인터는 C 언어의 핵심 기능이며, 임베디드 개발에서는 매우 자주 사용된다.

특히 다음과 같은 분야에서는 포인터 사용이 거의 필수적이다.

  • CAN/LIN 통신 데이터 처리
  • SPI/UART 버퍼 제어
  • Register 접근
  • Driver 개발
  • 메모리 제어
  • 함수 데이터 전달

하지만 많은 입문자들은 포인터를 처음 배우는 순간부터 어려움을 느낀다.

그 이유는 다음과 같다.

  • 메모리 주소 개념이 익숙하지 않음
  • *, & 기호 의미가 헷갈림
  • 변수와 주소 차이를 이해하기 어려움
  • 코드가 갑자기 복잡해 보임

하지만 포인터는 생각보다 어려운 개념이 아니다.

처음에는 낯설게 느껴질 수 있지만, 변수와 메모리 주소의 관계를 이해하면 포인터 개념도 자연스럽게 이해할 수 있다.

 

핵심은 단 하나이다.

"포인터는 메모리 주소를 저장하는 변수이다."

 

이번 장에서는 가장 기초적인 메모리 구조부터 포인터 선언, 사용 방법,

그리고 임베디드에서 왜 중요한지까지 차근차근 알아보자.

 

1. 변수는 메모리에 저장된다

 

우리가 사용하는 변수는 프로그램 내부 어딘가에 저장된다.

예를 들어 다음 코드가 있다고 가정해보자.

uint8 VehicleSpeed = 100U;

 

겉으로 보기에는 단순히 100이라는 값을 저장한 것처럼 보인다.

하지만 실제로는 RAM 메모리 공간 어딘가에 저장된다.

예를 들면 다음과 같은 형태이다.

변수 주소 값
VehicleSpeed 0x20000010 100

 

즉, 변수는 단순한 이름이 아니라 메모리 공간을 사용하는 데이터이다.

그리고 메모리 공간에는 각각 주소(Address)가 존재한다.

CPU는 바로 이 주소를 이용하여 데이터를 읽고 수정한다.

 

2. 주소(Address)란 무엇인가?

 

메모리는 수많은 저장 공간이 연결된 구조라고 생각하면 된다.

그리고 각각의 공간에는 번호가 존재한다.

이 번호를 메모리 주소(Address)라고 한다.

 

예를 들어 다음과 같은 메모리 공간이 있다고 가정해보자.

주소 데이터
0x20000000 10
0x20000001 20
0x20000002 30

 

CPU는 주소를 기준으로 데이터를 접근한다.

즉, 컴퓨터는 변수 이름이 아니라 실제 메모리 주소를 이용해 동작한다.

 

즉, 프로그램은 실제로 다음과 같은 방식으로 동작한다.

- 특정 주소에서 데이터 읽기

- 특정 주소에 데이터 쓰기

- 특정 주소 값 변경하기

 

포인터는 바로 이 주소를 직접 다루기 위한 기능이다.

 

3. 포인터(Pointer)란 무엇인가?

 

포인터는 메모리 주소를 저장하는 변수이다.

일반 변수는 데이터를 저장한다.

 

예를 들어:

uint8 Temp = 50U;

 

위 변수는 50이라는 값을 저장한다.

반면 포인터 변수는 값 자체가 아니라 주소를 저장한다.

 

일반 변수는 실제 데이터를 저장하지만, 포인터 변수는 메모리 주소를 저장한다.

이 차이를 이해하는 것이 가장 중요하다.

 

4. 주소를 얻는 방법 — & 연산자

 

변수의 주소를 얻기 위해 사용하는 연산자가 &이다.

 

예제를 보자.

uint8 VehicleSpeed = 100U;


여기서:

&VehicleSpeed

 

는 다음 의미이다.

"VehicleSpeed 변수의 메모리 주소"

 

즉,VehicleSpeed 은 실제 값이고, &VehicleSpeed 는 변수 주소이다.

입문자들이 가장 많이 헷갈리는 부분이 바로 이것이다.

 

변수 이름 자체는 데이터이고, &를 붙이면 주소가 된다.

 

5. 포인터 변수 선언

 

포인터 변수는 다음과 같이 선언한다.

uint8 *SpeedPtr;

 

이 코드를 처음 보면 * 때문에 어렵게 느껴질 수 있다.

하지만 의미를 나누어 보면 단순하다.

코드 의미
uint8 uint8 타입 데이터를 가리킴
* 포인터 변수
SpeedPtr 포인터 이름

 

즉, SpeedPtr는 "uint8 데이터의 주소를 저장하는 변수" 라는 의미이다.

 

6. 포인터에 주소 저장하기


포인터에는 주소를 저장해야 한다.
예제를 보자.

uint8 VehicleSpeed = 100U;
uint8 *SpeedPtr;

SpeedPtr = &VehicleSpeed;

 

이 코드는 다음 순서로 동작한다.

먼저 일반 변수 VehicleSpeed가 생성된다.

그리고 SpeedPtr이라는 포인터 변수가 생성된다.

이후 &VehicleSpeed 를 이용하여 VehicleSpeed의 주소를 얻는다.

그리고 그 주소를 포인터 변수에 저장한다.

 

즉, 현재 상태는 다음과 같다.

변수 저장 내용
VehicleSpeed 100
SpeedPtr VehicleSpeed 주소

 

여기서 중요한 점은 SpeedPtr 안에는 실제 값 100이 아니라 주소가 저장된다는 것이다.

 

7. 포인터로 실제 데이터 접근하기 — * 연산자


포인터가 저장한 주소의 실제 데이터에 접근할 때 사용하는 것이 *이다.
예제를 보자.

uint8 VehicleSpeed = 100U;
uint8 *SpeedPtr;

SpeedPtr = &VehicleSpeed;

 

현재 SpeedPtr은 VehicleSpeed의 주소를 가지고 있다.

이 상태에서 *SpeedPtr를 사용하면 주소가 가리키는 실제 데이터에 접근할 수 있다.

이 과정을 역참조(Dereference)라고 한다.

 

즉, 결과는 100이다.


정리하면 다음과 같다.

표현 의미
SpeedPtr 주소
*SpeedPtr 실제 값

 

이 개념이 포인터의 핵심이다.

 

8. 포인터를 이용한 데이터 변경

 

포인터는 원본 데이터도 수정할 수 있다.
예제를 보자.

uint8 VehicleSpeed = 100U;
uint8 *SpeedPtr;

SpeedPtr = &VehicleSpeed;

*SpeedPtr = 120U;

 

현재 SpeedPtr은 VehicleSpeed를 가리키고 있다.

 

따라서:

*SpeedPtr = 120U;

 

는 결국

VehicleSpeed = 120U;

 

와 같은 의미가 된다.


즉, 포인터를 이용하면 원본 데이터를 직접 수정할 수 있다.

이 특성 때문에 함수 데이터 수정, 버퍼 제어, Register 접근 등에 포인터가 사용된다.

 

9. 임베디드에서 포인터가 중요한 이유


임베디드 시스템은 하드웨어 메모리를 직접 제어한다.

예를 들어 CAN Driver, SPI Driver, UART Driver, ADC Driver 등은 대부분 메모리 주소 기반으로 동작한다.

특히 Register 제어는 대표적인 포인터 사용 예시이다.

 

하드웨어 Register는 특정 메모리 주소에 연결되어 있다.

이를 Memory Mapped I/O 방식이라고 한다.

 

CPU는 해당 주소를 읽거나 쓰면서 하드웨어를 제어한다.

즉, 임베디드 개발에서는 다음과 같은 작업이 매우 많다.

  • 특정 주소 접근
  • 메모리 데이터 읽기
  • 버퍼 데이터 전달
  • 통신 데이터 처리
  • DMA 메모리 연결

이 모든 과정에서 포인터가 핵심 역할을 한다.

 

10. 포인터 사용 시 주의사항

 

포인터는 강력한 기능이지만 잘못 사용하면 매우 위험하다.

가장 대표적인 문제는 잘못된 주소 접근이다.

예를 들어 초기화되지 않은 포인터를 사용하면 어떤 메모리를 접근할지 알 수 없다.

이 경우 다음과 같은 문제가 발생할 수 있다.

  • HardFault
  • Stack Corruption
  • 메모리 손상
  • 시스템 오동작
  • 예측 불가능한 버그

특히 임베디드 시스템에서는 치명적이다.

따라서 포인터는 반드시 올바른 주소를 저장한 상태에서 사용해야 한다.

 

정리

 

포인터는 메모리 주소를 저장하는 변수이다.

처음에는 어렵게 느껴질 수 있지만 핵심 개념은 단순하다.

  • 변수는 메모리에 저장된다.
  • 메모리에는 주소가 존재한다.
  • 포인터는 그 주소를 저장한다.
  • &는 주소를 얻는다.
  • *는 실제 데이터를 접근한다.

특히 임베디드 개발에서는 거의 모든 저수준 기능이 포인터 기반으로 동작한다.

따라서 포인터 개념은 반드시 확실하게 이해하고 넘어가는 것이 중요하다.

반응형