카드뉴스










C 언어 포인터는 주소를 저장하는 변수부터 봐야 합니다
C 언어 포인터를 처음 배울 때 가장 많이 헷갈리는 부분은 `*` 기호 자체보다 값과 주소를 한 문장에서 같이 다룬다는 점입니다. 일반 변수 `x`에는 정수값 10이 들어갈 수 있고, 그 변수는 메모리 어딘가의 주소를 가집니다. 포인터 변수 `p`는 10이라는 값을 저장하는 변수가 아니라, `x`가 놓인 주소를 저장하는 변수입니다. 이 구분이 흐려지면 함수 인자, 배열, 구조체 포인터를 만날 때마다 같은 혼란이 반복됩니다.
포인터를 읽을 때는 먼저 “이 변수 안에 실제 데이터가 들어 있는가, 아니면 다른 곳을 가리키는 주소가 들어 있는가”를 확인하는 습관이 필요합니다. 주소 연산자 `&`는 변수의 주소를 가져오고, 역참조 `*p`는 포인터가 들고 있는 주소를 따라가 그 위치의 값을 읽거나 씁니다. 같은 별표라도 선언부에서는 포인터 변수라는 표시이고, 실행문에서는 주소를 따라가는 동작이 됩니다.
함수에서 포인터를 쓰는 이유
C 함수는 기본적으로 값을 복사해서 인자로 받습니다. 그래서 `int x`를 함수에 넘기면 함수 안의 값 변경이 원본 `x`에 바로 반영되지 않을 수 있습니다. 반대로 `&x`처럼 주소를 넘기면 함수는 원본 변수가 있는 위치를 알게 되고, 역참조를 통해 그 값을 바꿀 수 있습니다. 센서 값을 읽어 여러 결과를 돌려주거나, 버퍼에 데이터를 채우거나, 드라이버 함수가 상태값을 갱신할 때 포인터가 자주 나오는 이유가 여기에 있습니다.
초보 단계에서는 포인터를 “어려운 문법”으로만 보지 말고 원본 데이터에 접근하는 통로로 이해하는 편이 좋습니다. 다만 주소를 넘긴다는 것은 원본을 바꿀 수 있다는 뜻이므로, 함수가 어떤 메모리를 수정하는지 코드 리뷰와 디버깅에서 꼭 확인해야 합니다.
배열과 포인터는 같지 않지만 연결됩니다
배열 이름은 많은 상황에서 첫 번째 요소의 주소처럼 동작합니다. 예를 들어 `arr`을 함수에 넘기면 배열 전체가 복사되는 것이 아니라 시작 주소가 전달되는 흐름으로 이해할 수 있습니다. 그래서 함수 안에서는 포인터와 인덱스를 이용해 버퍼의 각 요소에 접근합니다. 임베디드 C 코드에서 UART 수신 버퍼, ADC 샘플 배열, DMA 메모리 영역을 다룰 때 이 개념이 계속 등장합니다.
주의할 점은 배열과 포인터가 완전히 같은 것은 아니라는 점입니다. 배열은 정해진 크기의 연속된 메모리 공간이고, 포인터는 주소를 저장하는 변수입니다. `sizeof` 결과나 대입 가능 여부처럼 실제 동작이 달라지는 부분도 있습니다. 처음에는 “배열의 시작 주소를 포인터로 받아서 요소를 이동하며 접근한다” 정도의 흐름을 잡고, 이후에 포인터 연산과 메모리 경계 문제를 같이 공부하면 훨씬 덜 막힙니다.
디버깅할 때는 주소와 수명을 같이 봅니다
포인터 문제는 값이 틀린 것처럼 보이지만 실제로는 잘못된 주소를 따라간 결과인 경우가 많습니다. 지역 변수의 주소를 함수 밖에서 계속 쓰거나, 배열 범위를 넘어 접근하거나, 초기화하지 않은 포인터를 역참조하면 실행 결과가 매번 달라질 수 있습니다. 디버거에서 변수 값만 보지 말고 포인터에 들어 있는 주소, 그 주소가 가리키는 메모리, 데이터가 살아 있는 범위를 함께 확인해야 합니다. 임베디드에서는 잘못된 포인터 접근이 하드폴트나 메모리 오염으로 이어질 수 있어 더 조심해야 합니다.