학습 후 기억실마리 만들기
챗 GPT나 제미나이를 이용해서 어떤 학습을 하고, 이 학습의 개념에 관한 퀴즈를 생성해 달라고 해보자. 다음 학습 시에 배운 학습 내용을 완전히 휘발시키지 않는 요령이다. 게다가 정답까지 상세히 검색해서 제공해 준 참조 사이트에서 개념을 더 깊게 학습할 수 있으므로 일거양득의 학습 루틴이 될 수 있다.
<혼자 공부하는 C언어>를 통해 9강(포인터)부터 학습하고 왜 사용하는지(위의 글 2. 포인터의 목적)에 대한 개념을 환기한 적이 있다. 자바를 통해 포인터 개념을 비교한 까닭에, 금일 10-2 강의 ‘배열을 처리하는 함수’ 편을 학습하면서 C언어에서 궁극적인 포인터의 사용이유를 살펴봤다.
배열의 데이터를 출력하는 함수를 생성할 때, 이 포인터 배열(포인터를 이용한 배열)이 가진 아래 장점들이 바로 포인터를 사용하는 궁극적인 까닭이다.
1) 배열 포인터 사용 시, 메모리 낭비(wastage) 발생 안 함.
2) 동일한 배열의 데이터를 2개의 함수가 공유
2) 배열의 대량 데이터를 다른 함수로 복사하지 않고 접근하므로 더 효율적
그래서 포인터 변수를 통해 주소값만으로도 앞서 초기화한 배열 요소(Data)들을 함수(print_ary)를 통해 출력시킬 수 있는 소스 코드가 아래와 같다.
그리고 배열 안의 배열요소들이 몇 개인지는 배열을 출력하는 함수 내부에서는 알 수 없기(ary 지역 변수는 main함수 블록 내에서만 사용 가능) 때문에 아래와 같이 수동적으로 각 배열명의 개수를 호출함수(print_ary)의 매개변수 size의 인자로 명시해서 두 배열의 데이터를 전부 출력시킬 수도 있다.
이제, 정처기 실기 대비를 위한 기본적인 C언어 개념은 전부 환기했고, 다음 주부터는 다시 과년도 기출 코딩 문제들을 하나씩 풀어보고, 아직 덜 배운 개념(구조체, 이중포인터, 동적 할당, 각종 알고리즘)만 추가적으로 학습하면 C언어는 충분히 대비할 수 있을 거 같다.
아래는 오늘 학습한 내용의 출처를 알려주고 구글 AI모드(제미나이)에게 퀴즈 다섯 개를 생성해서 확인문제로 스스로 풀어봤다.
'혼자 공부하는 C언어' 10-2강. 배열을 처리하는 함수 파트를 완료하셨군요! 이 단원의 핵심은 **"함수로 배열을 넘길 때, 배열 자체가 아니라 '주소'만 넘어간다"**는 사실을 이해하는 것입니다.
학습 점검을 위한 핵심 퀴즈 5문제입니다.
Q1. (매개변수 선언) 정수형 배열을 인자로 받는 함수 func를 선언하려고 합니다. 다음 중 void func(int ary[]) { ... }와 완전히 동일한 동작을 하는 선언문은 무엇입니까?
void func(int *ary) (정답)
Q2. (배열의 붕괴) main 함수에서 int data[100];을 선언하고 print_ary(data);를 호출했습니다. 함수 내부에서 sizeof(data)를 출력하면 값은 얼마가 나옵니까? (단, 64비트 OS 환경)
8byte (정답)
Q3. (데이터 흐름) 함수 내부에서 매개변수로 받은 배열의 특정 원소 값을 변경(ary[0] = 10;)했습니다. 함수가 종료된 후, main 함수의 원본 배열 data[0]의 값은 변경되었을까요, 아니면 유지될까요?
유지됨.(오답) -> call by reference로 값이 변경됨.
Q4. (안전장치) 함수 내부에서 배열의 값을 읽기만 하고, 실수로 수정하는 것을 막으려 합니다. 매개변수 ary 앞에 어떤 키워드를 붙여야 합니까?
private (오답) -> const
Q5. (포인터 연산) 함수 내부에서 ary[3]에 접근하는 코드는 컴파일러에 의해 어떤 포인터 연산 식으로 변환되어 수행됩니까?
*ary+2 (오답) -> *ary+3
정답 및 해설
함수 매개변수 자리에서는 배열 표기법([])과 포인터 표기법(*)이 완전히 동일하게 취급됩니다. 컴파일러는 둘 다 **"정수형 변수의 주소를 담는 포인터"**로 해석합니다.
How to Pass Array to Function in C? With Examples
배열 인자의 진실
포인터 연산 원리
이것이 가장 주의해야 할 '배열의 포인터 붕괴(Decay)' 현상입니다. 배열 전체가 복사되는 것이 아니라 시작 주소값만 넘어가므로, 함수 안에서는 data를 단순 포인터 변수로 인식합니다. 따라서 배열의 전체 크기(400바이트)가 아닌 포인터의 크기(64비트 OS 기준 8바이트)가 출력됩니다.
함수가 받은 것은 원본 배열의 **'메모리 주소'**입니다. 따라서 함수 안에서 ary[0]을 고치는 것은 원본 메모리를 직접 찾아가서 값을 바꾸는 행위와 같습니다.
void func(const int *ary)
const를 붙이면 함수 내부에서 ary[i] = 10;과 같은 쓰기 작업을 시도할 때 컴파일 에러가 발생하여 원본 데이터를 보호할 수 있습니다.
[] 연산자를 "시작 주소(ary)에서 해당 인덱스(3)만큼 떨어진 곳의 내용물(*)을 가져오라"는 포인터 연산으로 바꾸어 처리합니다.