C언어 공부/개념 공부

2024.01.18 C언어 <메모리 동적할당 (3)>

코딩입문시작 2024. 1. 18. 18:57

학습목표 : 정수 하나를 저장하기 위해 동적 할당하는 것은 변수 선언보다 실행 시간이나 메모리 활용면에서 비효율적이다. 따라서 동적 할당은 활용 방법을 익히는 것이 무엇보다 중요하다.

  • 입력하는 문자열의 길이에 딱 맞는 메모리 공간을 확보할 수 있다.
  • 명령행 인수의 구현 방식을 이해할 수 있다.
  • 동적 할당 방식이 복잡할수록 할당 영역의 반환을 꼼꼼히 살펴봐야 한다.

동적 할당을 사용하면 데이터 맞춤 프로그래밍이 가능하다. 

 

구분 설명 상세
입력 문자열 처리 사용 예 입력 문자열의 길이에 맞는 저장 공간 확보
구현 방법 char str[80];
char* ps;
ps = (char*) malloc (strlen(str) + 1);
strcpy( (ps, str) );
명령행 인수 처리 main 함수의 인수 int main(int argc, char** argv);
의미 argc - 명령행 문자열의 수,
argv - 명령행의 문자열
사용 예 for(int i = 0; i < argc; i++) {
    printf("%s \n", argv[i]);
}

 

 

16.2.1 동적 할당을 사용한 문자열 처리

 

동적 할당은 프로그램의 효율을 높이기 위한 하나의 방법으로 사용된다. 

입력할 문자열의 길이를 알 수 없는 경우? 무조건 큰 배열 선언하는 것은 저장 공간의 낭비를 초래할 수 있다!

 

동적 할당을 하면 입력되는 문자열의 길이에 맞게 저장 공간을 사용할 수 있다.

 

 

길이가 다른 여러 개의 문자열을 효율적으로 처리하는 방법

문자열을 입력하기 전에는 그 길이를 알 수 없으므로 충분한 크기의 char 배열을 선언하고 문자열을 입력한다.

그리고 그 길이를 맞게 다시 동적으로 할당한 후에 입력한 문자열을 복사한다. 이 작업을 반복하면 여러 개의 문자열을 그 길이에 맞게 저장할 수 있다. 단, 동적 할당 영역을 연결할 포인터가 필요하므로 포인터 배열을 선언한다. 

 

strlen함수는 널문자를 제외하고 문자열의 길이를 계산하므로 malloc함수에 인수로 줄 때는 1을 더해서 널문자도 포함할 수 있도록 저장 공간을 할당해야 한다. malloc함수가 반환하는 주소는 포인터 배열의 요소에 저장하여 할당한 저장 공간을 연결한다. 마지막으로, 할당한 저장 공간에 입력한 문자열을 복사하면 길이가 다른 여러 개의 문자열을 포인터 배열로 묶어 처리할 수 있다. 

 

 

16.2.2 동적 할당 영역에 저장한 문자열을 함수로 처리하는 예

 

동적 할당한 저장 공간을 함수로 처리할 때는 할당한 공간의 구조를 잘 살펴야 한다. 

 

문자열 출력 부분을 함수로 바꿔 동적 할당 영역에 저장한 데이터를 함수로 처리하는 방법

char 배열의 문자열을 출력하는 함수는 배열명을 저장할 포인터를 배개변수로 선언한다. 마찬가지로 포인터 배열의 문자열을 출력하는 함수도 포인터 배열의 이름을 저장할 포인터 매개변수가 필요하다. 그렇다면 어떤 포인터가 필요할까? 배열명 str은 포인터 배열의 첫 번째 요소를 가리키므로 가리키는 것의 형태는 (char*) 형이다. 

 

배열명 str → [char*] [char*] [char*] [char*] ......

 

따라서, str을 저장할 매개변수로 (char*) 형을 가리키는 2중 포인터를 선언한다. 함수가 호출되고 매개변수 ps가 배열명 str을 저장하면 ps 역시 배열명과 같이 사용할 수 있으므로 함수 안에서 반복문을 사용하여 문자열을 출력한다. 

 main함수에서 문자열을 직접 출력할 때는 str이 배열명이므로 그 값을 바꿀 수 없다. 따라서 str[i] 와 같이 배열 표현을 사용하거나 *(str + i) 와 같이 정수를 더하면서 각 문자열을 출력할 수 밖에 없다. 그러나 배열명을 포인터에 저장하면 포인터 자신의 값을 바꿀 수 있으므로 매개변수를 하나씩 증가시키면서 문자열을 출력할 수 있다. (마지막 부분)

 

 

 

※ 여기서 포인터의 사용과 관련하여 주의할 점! 

포인터나 포인터 배열을 auto 지역 변수로 선언하면 쓰레기 값이 주소로 존재한다. 따라서 포인터 배열은 선언과 동시에 널포인터로 초기화하고 참조할 때 널포인터인지를 검사하면 보다 안정적인 프로그래밍이 가능하다. 물론 최소한 포인터 배열의 마지막 요소는 널 포인터의 자리로 남겨둬야 한다. 

 

이 예제는 동적 할당 영역을 마치 행의 길이가 가변적인 2차원의 char 배열처럼 쓴다.

이런 방식은 효과적인 메모리 사용을 가능하게 한다. 

 

 

16.2.3 main 함수의 명령행 인수 사용 (전혀 이해가 안가는 파트이다 ...)

 

프로그램의 실행 방법은 운영체제마다 다르다. 명령행에서 프로그램을 실행시킬 때는 프로그램의 이름 외에도 프로그램에 필요한 정보를 함께 줄 수 있는데 이들을 모두 명령행 인수 (command line argument) 라고 한다. 

 

 

 

운영체제가 명령행 인수를 프로그램의 main함수로 넘기는 방법을 통해 포인터로 동적 할당한 영역을 배열처럼 사용하는 예

 

프로젝트 > 속성 > 디버깅 > 명령인수 다루기

이해가 어려워, 참고 사이트 : C언어 main( ) 함수의 명령 인수 (argc, argv) : 네이버 블로그 (naver.com)  를 이용하였다.

 

C언어 main( ) 함수의 명령 인수 (argc, argv)

* main 함수의 매개변수는 보통 아무것도 사용하지 않지만(int main ( ) ) 경우에 따라서는 int main (int ...

blog.naver.com

 

main 함수 (프로그램의 진입점) 는 명령행 인수를 받기 위해 매개변수를 선언한다. 함수의 매개변수 (파라미터, 전달인자) 는 이 함수를 호출할 때 전달해주는 정보를 뜻하기 마련이다. 우리는 함수를 만들어놓고 호출할 때 바로 매개변수에 뭔가 정보를 넣어준다. 매개변수의 이름은 관례적으로 argc, argv를 사용한다.

  • int argc : 이것은 메인함수에 전달되는 정보의 개수를 의미하며 argument count의 줄인말이다.
  • char* argv[ ] : 이것은 메인함수에 전달되는 실질적인 정보로, 문자열의 배열을 의미한다. 첫 번째 문자열은 프로그램의 실행경로로 항상 고정이 되어 있으며 argument vector의 줄인말이다. 

명령행에서 프로그램을 실행시키면 운영체제는 명령행 인수를 가공하여 문자열의 형태로 메모리에 저장하고 포인터 배열로 연결한 후에 포인터 배열의 시작 위치를 실행 프로그램의 main함수에 넘긴다. 이때 명령행 인수의 개수도 함께 전달된다. 

 

 

16.2.4 명령행 인수를 사용한 문자열 입력 예

 

 

 

먼저 명령행 인수를 통해 저장할 수 있는 문자열의 최대 수를 결정한다.

프로그램을 실행할 때 명령행에서 두 번째로 입력하는 문자열은 argv[1]에 연결된다. 

argv → argv[0] → strings \0

        → argv[1] → 5 \0

        → argv[2] → 0 

프로그램은 argv[1]이 연결하고 있는 문자열을 사용하여 앞으로 입력될 문자열을 연결할 포인터 배열의 크기로 사용한다. 그런데 argv[1]이 연결하고 있는 것은 문자열이므로 이 문자열을 정수로 바꾸는 과정이 필요하다. 숫자의 형태로 된 문자열을 정수로 바꾸기 위하여 atoi함수를 호출한다. 이 함수는 문자열의 주소를 인수로 받아서 문자열을 정수로 바꾼 후에 반환하며 헤더 파일은 stdlib.h를 사용한다. 

 

※ int atoi(char*);  // 문자열을 정수로 변환한 후 반환

 

정수로 바꾼 후에는 일단 max변수에 저장하여 max값 만큼 배열 요소를 가지는 포인터 배열을 동적으로 할당한다. 즉, 문자열을 연결할 포인터 배열의 크기를 명령행 인수를 통해 결정한다. 

두 번째는 준비한 포인터 배열에 문자열을 입력하여 연결한다. 세 번째는 입력된 모든 문자열을 함수로 화면에 출력한다.

마지막으로 동적 할당 영역을 반환한다.