Transcript Chap18
Name
Title
Company Name
프로젝트와 분할 컴파일
프로그램 실행 중에 기억공간을 확보하는 동적 할당
동적 기억장소 할당과 해제
전처리기
#include와 헤더 파일
#define과 매크로
미리 정의된 매크로
조건 컴파일
배포 프로그램
규모가 있는 하나의 프로젝트 프로그램을 함수단위로 구분하여 작성한 것을
프로그램 모듈(module)이라 하는데 모듈별로 개발된 프로그램들을 각각
컴파일을 하고, 링크과정을 거쳐 하나로 통합된 프로그램으로 개발한다.
분할 컴파일이란 각 모듈별 프로그램 파일로 나누어 컴파일 하는 것을 의미.
이러한 방법을 이용하면 프로그램의 일부를 변경하기 위해 프로그램 전체를
컴파일 해야 하는 번거로움을 덜 수 있다.
프로그램을 개발할 때 위와 같이 프로그램 모듈별로 나누어 개발하는 방법은
어린 아이들이 흔하게 가지고 노는 합체 로봇에 비유할 수 있다.
개별 로봇들은 다름 대로의 기능과 역할을 하도록 만들어져 있지만 이들을
모두 합체하면 하나의 완전한 로봇(프로그램)으로 완성되는 것과 같다.
모듈별 프로그램들이 이미 작성되어 저장된 상태에서도 분할 컴파일이
가능하지만 모든 프로그램을 새롭게 작성한다고 가정.
다음과 같은 두 개의 프로그램(main.cpp, func01.cpp)을 작성하여 분할
컴파일을 한다고 가정.
[단계 1]
프로젝트 이름을
project1이라 하고
다음과 같이
main.cpp를 작성
[단계 2]
main.cpp 생성 후에
다음과 같이 솔루션
탐색기에서 [소스
파일]에 마우스
오른쪽 버튼을 눌러
[추가]→[새 항목]을
선택하여 파일이름
func01.cpp의
프로그램을 작성.
[단계 3]
필요한 두 개의 소스 파일을 작성했으므로 빌드과정을 처리.
지금까지는 하나의 프로그램에 대해서만 솔루션 빌드를 하였으나 프로젝트에
포함된 소소 파일을 모두 빌드하려면 다음과 같이 [빌드]→[프로젝트 이름]
빌드를 선택. 프로젝트 이름은 project1
[단계 4]
프로젝트 빌드를 선택하여 빌드과정이 처리되고, 빌드 결과는 다음과 같다.
아래 부분을 보면 두 개의 소스 파일을 모두 빌드하여 하나의 프로그램인
project1.exe가 생성되었음을 알 수 있다.
[실행결과]
함수 main에서 data=9;로 초기호한 후에 함수 func01을 호출.
함수 func01에서 전역 변수인 data에 대해 값을 20으로 바꾸고 다시 함수
main으로 돌아와 data의 값을 출력하면 20으로 바꾸어져 있는 것을 알 수 있다.
지금까지 프로그램에서 사용한 모든 변수나 배열 등은 컴파일 과정에서
기억공간에 대한 크기가 결정되는데 이를 정적 할당이라 한다.
동적 할당은 컴파일과정이 아니라 프로그램의 실행 중에 기억공간을
확보하는 것이다.
어떤 사람의 주소는 짧을 수도 있고 경우에 따라 길어질 수 있다. 따라서 모든
경우가 처리될 수 있도록 충분한 크기의 기억 공간을 확보해야 하기 때문에
비교적 적은 문자열에 대해서는 기억공간을 낭비하게 된다. 이와 같은 경우에
프로그램의 실행 중에 입력할 해당 데이터의 크기에 따라 기억 공간을
확보하는 동적할당을 사용한다면 기억공간의 낭비를 줄일 수 있다.
동적할당은 프로그램 실행 중에 기억 공간이 확보되는 것이므로 컴파일
과정에서는 그 크기를 알 수 없으며, 변수의 이름 또한 갖지 못한다.
따라서 실행 중에 동적 할당을 통해 기억 공간이 확보되면 그 공간을 사용하기
위해 할당된 기억공간의 주소를 사용해야 하므로 포인터를 사용한다.
동적으로 기억장소를 확보하는데 사용하는 함수는 malloc(memory
allocation)이고, 확보한 메모리를 해제하는 함수는 free로서 이 함수를 사용할
때는 헤더파일 <stdlib.h>가 필요하다.
함수 malloc의 인자인 size_t는 데이터 형의 크기를 의미하며, unsigned int 형.
동적 할당은 변수의 이름이 없으며, 실행 중에 기억 공간을 확보하고, 그
공간을 사용하기 위해 할당된 기억공간의 주소를 사용해야 하므로 포인터를
사용한다. 따라서 malloc은 할당된 메모리에 대한 시작 주소를 반환하고
반환값의 데이터 형은 void 형으로서 모든 데이터 형의 포인터를 이용할 수
있다.
동적으로 할당된 기억 공간은 컴파일러가 그 크기를 모르므로 프로그램에서
더 이상 필요하지 않을 경우 해제해야 하는데 이때 사용하는 함수가 free이며
포인터를 인자로 사용한다.
입력할 문자열에 대한 길이(byte
수)를 입력 받아 그 크기만큼
동적으로 기억공간을 할당하여
저장하는 방법
실행 결과
구조체 변수에 데이터를 저장하는 방법으로 일반 변수와 마찬가지로
키보드를 통하여 입력할 수 있다.
문자열을 키보드를 통해 입력할 때 사용할 수 있는 라이브러리 함수로
scanf, gets 그리고 fgets를 이용할 수 있다.
이름에는 공백이 사용될 수 있는데 scanf의 경우는 공백 이전의 문자열만
저장되므로 주의해야 한다.
포인터는 단지 메모리 주소를 저장하는 기능만 있기 때문에 키보드 입력을
통해 포인터에 문자열을 저장하기 위해서는 반드시 메모리 할당을 해야 한다.
실행 결과
포인터는 단지 메모리 주소를 저장하는 기능만 있기 때문에 키보드 입력을
통해 포인터에 문자열을 저장하기 위해서는 반드시 메모리 할당을 해야 한다.
구조체 형 포인터 p1에 대해 emplyee 만큼
(28 byte)의 기억 공간을 동적으로 할당
실행 결과
C 프로그램을 작성할 때 프로그램의 처음 부분에 항상 사용했던 #include나
#define 등을 전처리기 또는 선행처리기(preprocessor)라 한다.
전처리기는 어떤 처리를 하기 이전(pre)에 하는 처리를 말하며, 컴파일을 하기
이전에 어떤 내용을 미리 처리하게 된다.
소스 프로그램에 포함된 전처리기 지시자 #include는 헤더 파일과 같이 현재
작성중인 프로그램 파일의 외부에 있는 파일들을 컴파일 하기 이전에
불러들이는 역할을 한다. 전처리기 지시자인 #define은 컴파일을 하기 이전에
매크로 상수나 매크로 함수를 정의하는데 사용한다.
모든 프로그램의 첫 부분에 사용하였던 #include는 프로그램 외부에 존재하는
파일(헤더 파일 등)을 불러오는데 사용한다.
프로그램에서 사용한 라이브러리 함수인 printf나 scanf가 비록 프로그램 안에
정의되어 있지 않았음에도 불구하고 사용할 수 있었던 이유는 그 함수의
원형(prototype)이 정의되어 있는 헤더 파일인 <stdio.h>를 불러(include)왔기
때문이다.
프로그램에서 자주 사용하게 될 함수나 구조체등을 필요할 때 마다 매번 main
함수가 정의된 프로그램 안에 정의하여 사용하는 것은 번거로운 일이며,
프로그램의 수정에 있어서도 효율적이지 못하다.
따라서 구조체 정의가 포함된 파일을 헤더 파일로 만들어 필요할 때마다
#include를 이용하여 불러들이면 프로그램의 관리나 수정이 쉬워진다.
구구단을 출력하는 함수(gugudan.h)를 헤더 파일에 정의하여 사용하는 방법
먼저 소스 프로그램(main.cpp)을 생성하고,
새 항목을 추가하는 과정에서 다음과 같이
파일 형식을 헤더 파일(.h)로 선택하여
다음의 헤더 파일(gugudan.h)의 내용을
입력하여 저장한다.
헤더 파일에 대해서는 앞서 분할 컴파일처럼 같이 컴파일을 할 필요가 없다.
소스 프로그램(main.cpp)만 솔루션 빌드로 처리하면 컴파일 과정에서 헤더
파일(gugudan.h)을 불러와서 컴파일을 처리하기 때문이다.
#define 은 프로그램에서 자주 사용하게 될 상수를 정의하거나 비교적 간단한
함수를 정의하는데 사용한다.
매크로 상수이름은 일반 변수이름과 구별하기 위해 주로 대문자로 표시
#define을 이용하여 상수를 정의하는 것은 매크로 상수라 한다.
상수에는 숫자 또는 문자, 문자열을 사용할 수 있다.
실행 결과
일반 변수와 매크로 상수의 비교
#define을 이용하여 함수를 정의하는 것을 매크로 함수라 한다.
매크로 함수의 이름도 일반 함수의 이름과 구별하기 위해 주로 대문자를
사용한다.
매크로 함수는 일반 함수와 같이 인자를 사용할 수 있는 공통점이 있으나,
함수의 결과값을 되돌려주는 return문이 사용되지 않는 차이점이 있다.
다음의 예는 어떤 값의 제곱을 계산하는 매크로 함수 SQUARE를 정의하여
사용한 것이다. 매크로 함수를 정의할 때 주의해야 하는 것은 함수의
이름인SQUARE와 인자를 리스트를 정의하는 (x) 사이에 공백이 없어야 한다.
실행 결과
다음과 같이 매크로 함수의 인자로 x+1과 같은 연산식이 포함된 프로그램의
결과는 어떻게 나올 것인가?
실행 결과
프로그램 수정과
실행 결과
일반 변수와 매크로 상수의 비교
일반 함수와 매크로 함수의 비교
선행처리기에서 처리할 수 있는 매크로 중에서 ANSI에서 미리 정의된
매크로(predefined macro)가 있다.
미리 정의된 매크로는 컴파일러가 제공하는 매크로로서 컴파일러가 현재
상황이나 컴파일 중에 참고할만한 정보를 프로그래머에게 알려주기 위한
용도로 사용한다.
이러한 매크로는 #define으로 정의하지 않아도 사용할 수 있지만 다시 정의할
수는 없다.
실행 결과
조건 컴파일(conditional compilation)이란 컴파일을 할 때 프로그램의 일부분을
선택적으로 컴파일 한다는 의미이다.
즉, 주어진 조건에 따라 프로그램의 어떤 부분을 컴파일 하거나 컴파일 하지
않도록 할 수 있다는 것이다.
조건 컴파일의 지시자는 조건문의 if의 사용방법과 유사하지만 중괄호 {}를
사용하지 않는 차이가 있다. 조건 컴파일은 변수, 함수 또는 매크로가
중복되는 것을 피하는데 사용한다.
조건 컴파일 지시자는 조건에 따라 다르게 컴파일 할 수 있기 때문에 하나의
프로그램 코드를 가지고 서로 다른 실행 파일을 만들어낼 수 있다.
그러므로 예를 들어 사용자의 수준에 맞는 프로그램을 구분하여 만들 경우에
프로젝트 자체를 구분하여 개발하기 보다는 조건 컴파일을 사용하여 실행
파일을 구분하여 생성할 수 있다.
#if 지시자 다음에는 if문과 마찬가지로 조건식이 필요하다.
주어진 조건식이 참일 경우에 프로그램 코드 ① 부분이 컴파일 되며, 조건식이
참이 아닐 경우에는 프로그램 코드 ② 부분이 컴파일 된다.
따라서 조건문인 if문과 같이 중괄호 {}를 사용하지 않으며 대신 조건 컴파일의
마지막 부분을 나타내는 #endif가 반드시 사용되어야 한다.
실행 결과
#elif 지시자는 "else if"의 약어로서 다음과 같이 여러 개의 선택 조건 중에서
참인 조건식에 포함된 프로그램 코드만을 선택하여 컴파일 하는데 사용한다.
다음은 사용자의 수준에 따라 헤더 파일을 선택적으로 불러들여 컴파일을
하는 예이다.
#ifdef는 "if define"의 약어로서 #if와는 달리 매크로 이름이 사용된다.
#ifdef 다음에는 조건이 되는 매크로 이름을 쓰고, #ifdef와 #endif 사이에
조건적으로 컴파일 할 프로그램을 작성한다.
다음의 예에서와 같이 만약 #ifdef 다음에 명시된 매크로 이름이 프로그램
내에서 매크로로 존재한다면 조건 컴파일 영역에 포함된 프로그램 ①은
컴파일 되며, 그렇지 않다면 프로그램 코드 ②가 컴파일 된다. 만약 #else가
없는 경우에 매크로 이름이 존재하지 않는다면 프로그램 코드 ① 부분은
전처리기 과정에서 생략되므로 프로그램 코드가 없는 것으로 컴파일 된다.
조건적으로 컴파일 할 내용은 #if와 같이 #ifdef과 #endif 사이에 있게 되므로
중괄호 {}가 필요 없다.
#ifdef의 사용 예
#ifndef는 #ifdef와 반대의 역할을 하는 지시자이다.
#ifdef는 매크로가 정의되어 있을 때에만 컴파일 하지만 반대로 #ifndef는
매크로가 정의되어 있지 않는 경우에만 컴파일을 한다.
조건문인 if문과 마찬가지로 #ifdef와 #endif 사이에 #else를 넣을 수도 있으며
이 경우 #else 이하의 프로그램 코드는 그 외의 조건인 경우를 처리한다.
원시 프로그램을 작성하고 솔루션 빌드과정을 마치면 실행 가능한 *.exe
파일을 생성한다. 예를 들어 폴더 C:\test에서 [예제 18-1]의 프로그램에 대해
다음과 같이 프로젝트 이름을 dynamic, 소스 파일 이름을 malloc.cpp로 하여
솔루션 빌드 과정을 마쳤다면 생성된 폴더와 파일은 그림과 같이 Debug라는
폴더에 dynamic.exe라는 실행파일이 생성된다.
컴파일 모드 : 디버그 모드(debug mode)와 릴리즈 모드(release mode)
디버그 모드 : 프로그램을 개발하는 단계에서 사용하는 모드
릴리즈 모드 : 프로그램의 개발을 마치고 배포용 프로그램을 만들 경우의 모드
지금까지 사용한 컴파일 모드는 디버그 모드로서 디버그에 필요한 모든
정보를 포함하여 파일을 컴파일하기 때문에 실행 프로그램의 크기가
커지므로 속도에 영향을 받습니다. 반면 릴리즈 모드는 코드에 필요한 부분을
실행 파일로 컴파일하기 때문에 최적화되어 실행 프로그램의 크기도
작아지고, 속도도 개선된다.
Visual C++의 환경에서 C 언어가 아닌 C++ 언어를 이용하여 작성한 실행
프로그램을 배포할 경우에는 반드시 릴리즈 모드로 전환하여 컴파일을
해야만 Visual C++가 설치되지 않은 컴퓨터에서도 실행이 가능하게 된다.
그러나 C 언어로 작성한 프로그램은 디버그 모드로 컴파일을 하던지 릴리즈
모드로 컴파일을 하던지 실행 상에서는 큰 차이는 없지만 릴리즈 모드로
컴파일 했을 경우에 실행 프로그램의 파일 크기는 현저하게 작아진다.
다음과 같이 Release를 선택한 다음 솔루션 다시 빌드를 하면 폴더 Release와
실행 프로그램이 생성된다. 디버그 모드의 실행 프로그램과 릴리즈 모드의
실행 프로그램의 파일 크기를 비교하시오.
디버그 모드의 실행 프로그램
릴리즈 모드의 실행 프로그램
프로그램을 빌드하고 실행했을 경우에는 실행이 종료됨과 동시에 마지막
부분에 "계속하라면 아무 키나 누르십시오..."라고 표시되지만 윈도우
탐색기로 실행 프로그램(dynamic.exe)을 더블 클릭하여 실행했을 경우에는
프로그램의 종료와 동시에 창이 닫쳐지므로 결과를 확인할 수 없다.
[해결방법]
배포용 실행 프로그램에서 이와 같은 현상이 나타나는 것을 방지하기
위해서는 프로그램의 종료 직전에 실행을 잠시 멈추게 하기 위하여
return 0;의 바로 앞에 DOS 명령인 "pause"를 system("pause");로 추가한다.
DOS 명령을 실행하는 함수 system은 헤더 파일 <stdlib.h>를 필요로 한다.
프로그램의 종료 직전에
실행을 잠시 멈추게 하기
위하여
return 0;의 바로 앞에 DOS
명령인 "pause"를
system("pause");로 추가
분할 컴파일이란?
모듈별로 완성된 원시 프로그램들을 하나의 실행 프로그램으로 만들기
위해 프로젝트 단위로 만들어 컴파일하는 방법이다. 소스 파일 각각에 대해
컴파일을 하지 않고 프로젝트의 이름으로 빌드를 하면 프로젝트 안에
포함된 모든 소스 파일에 대해 컴파일과 링크과정을 거쳐 실행 프로그램을
생성한다.
동적할당이란?
프로그램에서 사용한 모든 변수나
배열 등은 컴파일 과정에서
기억공간에 대한 크기가
결정되는데 이를 정적 할당이라
하고 동적 할당은 컴파일과정이
아니라 프로그램의 실행 중에
기억공간을 확보하는 것을 말한다.
동적으로 기억공간을 할당할 때는
함수 malloc을 사용하고, 반대로
동적 할당부분을 해제할 때는 함수
free를 사용한다.
구조체 멤버에 대한 동적할당
포인터는 단지 메모리 주소를 저장하는 기능만 있기 때문에 키보드 입력을
통해 포인터에 문자열을 저장하기 위해서는 반드시 메모리 할당을 해야 한다.
구조체 형 포인터에 대한 동적할당
포인터는 단지 메모리 주소를 저장하는 기능만 있기 때문에 키보드 입력을
통해 포인터에 문자열을 저장하기 위해서는 반드시 메모리 할당을 해야 한다.
구조체 형 포인터 p1에 대해 emplyee 만큼
(28 byte)의 기억 공간을 동적으로 할당
전처리기란?
C 프로그램을 작성할 때 프로그램의 처음 부분에 항상 사용했던 #include나
#define 등을 전처리기 또는 선행처리기(preprocessor)라 한다.
전처리기는 어떤 처리를 하기 이전(pre)에 하는 처리를 말하며, 컴파일을 하기
이전에 어떤 내용을 미리 처리하게 된다.
전처리기 지시자
소스 프로그램에 포함된 전처리기 지시자 #include는 헤더 파일과 같이 현재
작성중인 프로그램 파일의 외부에 있는 파일들을 컴파일 하기 이전에
불러들이는 역할을 한다. 전처리기 지시자인 #define은 컴파일을 하기 이전에
매크로 상수나 매크로 함수를 정의하는데 사용한다.
#include와 헤더 파일
모든 프로그램의 첫 부분에 사용하였던 #include는 프로그램 외부에 존재하는
파일(헤더 파일 등)을 불러오는데 사용한다.
프로그램에서 자주 사용하게 될 함수나 구조체등을 필요할 때 마다 매번 main
함수가 정의된 프로그램 안에 정의하여 사용하는 것은 번거로운 일이며,
프로그램의 수정에 있어서도 효율적이지 못하다. 따라서 구조체 정의가
포함된 파일을 헤더 파일로 만들어 필요할 때마다 #include를 이용하여
불러들이면 프로그램의 관리나 수정이 쉬워진다.
#define과 매크로
#define 은 프로그램에서 자주 사용하게 될 상수를 정의하거나 비교적 간단한
함수를 정의하는데 사용한다.
매크로 상수이름은 일반 변수이름과 구별하기 위해 주로 대문자로 표시
#define을 이용하여 상수를 정의하는 것은 매크로 상수라 하고
#define을 이용하여 함수를 정의하는 것은 매크로 함수라 한다.
#define과 매크로 상수의 예
#define과 매크로 함수의 예
매크로 함수를 정의할 때 주의해야 하는 것은 함수의 이름인
SQUARE와 인자를 리스트를 정의하는 (x) 사이에 공백이 없어야 한다
매크로 상수와 매크로 함수 정리
일반 변수와 매크로 상수의 비교
일반 함수와 매크로 함수의 비교
미리 정의된 매크로의 예
조건 컴파일
조건 컴파일(conditional compilation)이란 컴파일을 할 때 프로그램의 일부분을
선택적으로 컴파일 한다는 의미이다.
즉, 주어진 조건에 따라 프로그램의 어떤 부분을 컴파일 하거나 컴파일 하지
않도록 할 수 있다는 것이다.
배포 프로그램과 컴파일 모드 전환
디버그 모드 : 프로그램을 개발하는 단계에서 사용하는 모드
릴리즈 모드 : 프로그램의 개발을 마치고 배포용 프로그램을 만들 경우의 모드
릴리즈 모드의 실행 프로그램
배포용 프로그램을 만들 경우 유의사항
프로그램의 종료 직전에
실행을 잠시 멈추게 하기
위하여
return 0;의 바로 앞에 DOS
명령인 "pause"를
system("pause");로 추가