컴파일 과정



Visual Studio에서 우리는 실행할때 F5(또는 Ctrl+F5)를 눌러서 우리가 만든 소스코드를 실행시켜봤죠? 우리는 너무 쉽게 프로그램을 실행시킨다고 생각할 수 있지만 의외로 몇몇 단계를 거치고 있습니다.


이번 시간에는 컴퓨터 실행파일이 어떻게 생겨나는지에 대해서 알아보도록 합시다. 


우리가 실행파일을 생성하는데까지는 아래와 같은 과정을 거치게 됩니다. 어? program.c와 program.exe는 알겠는데 나머지 파일들은 무엇일까요?





이 파일들의 정체를 알아내기 위해서 잠시 리눅스를 사용하도록 하겠습니다. 여러분들은 어떤 파일에 어떤 내용들이 기록되는지에 대해서 눈여겨 보면 될 것 같네요.


다음의 소스코드가 어떻게 실행파일로 변하는지 알아보지요.




#include <stdio.h>
#define A 10
#define B 20
int main(){
        int a=A;
        int b=B;
        int c=a+b;
        printf("%d + %d = %d\n",a,b,c);
}


전처리기(Preprocessor)


전처리기 구문(#으로 시작하는 구문)을 처리하는 것이 바로 전처리기라고 하는데요. 일반적으로 #으로 시작하는 부분을 거의 항상 사용합니다. 그것이 언제냐면 바로 #include지요. 너무나도 소중한 printf를 사용하기 위해서는 항상 #include <stdio.h>를 항상 명시해주어야 하죠.


#include를 통해서 stdio.h의 내용이 그대로 들어오게 됩니다!


또한 위의 코드에서 우리는 #define A 10 과 같은 줄을 볼 수 있는데요. 여기서 전처리기는 A라는 부분을 단순히 10으로 치환합니다.


자. 그렇다면 전처리 과정을 끝낸 program.i는 어떻게 변할까요?


gcc -E program.c -o program.i


위의 명령어로 program.i의 내용을 살펴봅시다.


program.i

# 1 "program.c"

# 1 "<built-in>"

# 1 "<command-line>"

# 1 "/usr/include/stdc-predef.h" 1 3 4

# 1 "<command-line>" 2

# 1 "program.c"

....
extern int printf (const char *__restrict __format, ...);
...

int main(){
 int a=10;
 int b=20;
 int c=a+b;
 printf("%d + %d = %d\n",a,b,c);
}


보세요. stdio.h의 내용이 main위의 그대로 들어오지요? 또한 #define A 10과 같은 내용은 없어지고 A가 10으로 치환된것을 알 수 있습니다.


전처리기는 너무나도 단순한 역할을 하는 군요.


중요한것은 전처리기가 컴파일 단계 맨 처음 단계라는 것을 기억하셔야합니다. 그래야지 전처리를 통한 조건부 컴파일을 이해하게 됩니다.



컴파일러(Compiler)

이제 전처리기를 거쳤으니 컴파일러로 컴파일해줍니다. 컴파일러는 고수준언어를 저수준언어로 나타내는 역할을 수행합니다. 저수준언어라는 것은 기계어와 가장 가까운 언어입니다.


이제 program.i로부터 어떻게 program.s가 생겨나는지 보도록 합시다.


gcc -S program.i -o program.s



program.s

.file   "program.c"

        .section        .rodata

.LC0:

        .string "%d + %d = %d\n"

        .text

        .globl  main

        .type   main, @function

main:

.LFB0:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        subq    $16, %rsp

        movl    $10, -4(%rbp)

        movl    $20, -8(%rbp)

        movl    -8(%rbp), %eax

        movl    -4(%rbp), %edx

        addl    %edx, %eax

        movl    %eax, -12(%rbp)

        movl    -12(%rbp), %ecx

        movl    -8(%rbp), %edx

        movl    -4(%rbp), %eax

        movl    %eax, %esi

        movl    $.LC0, %edi

        movl    $0, %eax

        call    printf

        leave

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE0:

        .size   main, .-main

        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)"

        .section        .note.GNU-stack,"",@progbits


뭐 저도 잘 모르겠습니다. 그냥 저수준언어로 변한것 밖에는 모르겠네요. 

근데 "%d + %d = %d\n" 는 우리가 printf에 썼던 문자열이라는 것을 알 수 있네요.

이것이 컴파일러가 하는 역할입니다. 이제 파일을 오브젝트파일로 변환하는 어셈블러를 보도록 합니다.


어셈블러(Assembler)

이제 완전히 기계어로 바꾸어 주는 역할을 합니다. 우리가 읽을 수 없거든요. 다음의 명령어를 통해서 기계어 파일을 만들고 확인해보도록 하죠.


gcc -c program.s -o program.o


program.o

^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@È^B^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@

^@UH<89>åH<83>ì^PÇEü

^@^@^@ÇEø^T^@^@^@<8b>Eø<8b>Uü^AÐ<89>Eô<8b>Mô<8b>Uø<8b>Eü<89>Æ¿^@^@^@^@¸^@^@^@^@è^@^@^@^@ÉÃ%d + %d = %d

^@^@GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)^@^@^@^@^@^@^@^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@^@^\^@^@^@^@^@^@^@=^@^@^@^@A^N^P<86>^BC^M^Fx^L^G^H^@^@^@^@.symtab^@.strtab^@.shstrtab^@.rela.text^@.data^@.bss^@.rodata^@.comment^@.note.GNU-stack^@.rela.eh_frame^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^D^@ñÿ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^E^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^G^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^K^@^@^@^R^@^A^@^@^@^@^@^@^@^@^@=^@^@^@^@^@^@^@^P^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@program.c^@main^@printf^@^@-^@^@^@^@^@^@^@

^@^@^@^E^@^@^@^@^@^@^@^@^@^@^@7^@^@^@^@^@^@^@^B^@^@^@



^@^(못 읽겠지?) 뭔가 비웃는것 같은 문자만 있네요. 네, 몰라요.

컴퓨터만 알고 있습니다.




hexdump로 볼까요?


hexdump -C program.o


00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|

00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..>.............|

00000020  00 00 00 00 00 00 00 00  c8 02 00 00 00 00 00 00  |................|

00000030  00 00 00 00 40 00 00 00  00 00 40 00 0d 00 0a 00  |....@.....@.....|

00000040  55 48 89 e5 48 83 ec 10  c7 45 fc 0a 00 00 00 c7  |UH..H....E......|

00000050  45 f8 14 00 00 00 8b 45  f8 8b 55 fc 01 d0 89 45  |E......E..U....E|

00000060  f4 8b 4d f4 8b 55 f8 8b  45 fc 89 c6 bf 00 00 00  |..M..U..E.......|

00000070  00 b8 00 00 00 00 e8 00  00 00 00 c9 c3 25 64 20  |.............%d |

00000080  2b 20 25 64 20 3d 20 25  64 0a 00 00 47 43 43 3a  |+ %d = %d...GCC:|

00000090  20 28 47 4e 55 29 20 34  2e 38 2e 35 20 32 30 31  | (GNU) 4.8.5 201|

000000a0  35 30 36 32 33 20 28 52  65 64 20 48 61 74 20 34  |50623 (Red Hat 4|

000000b0  2e 38 2e 35 2d 31 36 29  00 00 00 00 00 00 00 00  |.8.5-16)........|

000000c0  14 00 00 00 00 00 00 00  01 7a 52 00 01 78 10 01  |.........zR..x..|

000000d0  1b 0c 07 08 90 01 00 00  1c 00 00 00 1c 00 00 00  |................|

000000e0  00 00 00 00 3d 00 00 00  00 41 0e 10 86 02 43 0d  |....=....A....C.|

000000f0  06 78 0c 07 08 00 00 00  00 2e 73 79 6d 74 61 62  |.x........symtab|

00000100  00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74  |..strtab..shstrt|

00000110  61 62 00 2e 72 65 6c 61  2e 74 65 78 74 00 2e 64  |ab..rela.text..d|

00000120  61 74 61 00 2e 62 73 73  00 2e 72 6f 64 61 74 61  |ata..bss..rodata|

00000130  00 2e 63 6f 6d 6d 65 6e  74 00 2e 6e 6f 74 65 2e  |..comment..note.|

00000140  47 4e 55 2d 73 74 61 63  6b 00 2e 72 65 6c 61 2e  |GNU-stack..rela.|

00000150  65 68 5f 66 72 61 6d 65  00 00 00 00 00 00 00 00  |eh_frame........|

00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000170  00 00 00 00 00 00 00 00  01 00 00 00 04 00 f1 ff  |................|

00000180  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000190  00 00 00 00 03 00 01 00  00 00 00 00 00 00 00 00  |................|

000001a0  00 00 00 00 00 00 00 00  00 00 00 00 03 00 03 00  |................|

000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000001c0  00 00 00 00 03 00 04 00  00 00 00 00 00 00 00 00  |................|

000001d0  00 00 00 00 00 00 00 00  00 00 00 00 03 00 05 00  |................|

000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000001f0  00 00 00 00 03 00 07 00  00 00 00 00 00 00 00 00  |................|

00000200  00 00 00 00 00 00 00 00  00 00 00 00 03 00 08 00  |................|

00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000220  00 00 00 00 03 00 06 00  00 00 00 00 00 00 00 00  |................|

00000230  00 00 00 00 00 00 00 00  0b 00 00 00 12 00 01 00  |................|

00000240  00 00 00 00 00 00 00 00  3d 00 00 00 00 00 00 00  |........=.......|

00000250  10 00 00 00 10 00 00 00  00 00 00 00 00 00 00 00  |................|

00000260  00 00 00 00 00 00 00 00  00 70 72 6f 67 72 61 6d  |.........program|

00000270  2e 63 00 6d 61 69 6e 00  70 72 69 6e 74 66 00 00  |.c.main.printf..|

00000280  2d 00 00 00 00 00 00 00  0a 00 00 00 05 00 00 00  |-...............|

00000290  00 00 00 00 00 00 00 00  37 00 00 00 00 00 00 00  |........7.......|

000002a0  02 00 00 00 0a 00 00 00  fc ff ff ff ff ff ff ff  |................|

000002b0  20 00 00 00 00 00 00 00  02 00 00 00 02 00 00 00  | ...............|

000002c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

*

00000300  00 00 00 00 00 00 00 00  20 00 00 00 01 00 00 00  |........ .......|

00000310  06 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000320  40 00 00 00 00 00 00 00  3d 00 00 00 00 00 00 00  |@.......=.......|

00000330  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000340  00 00 00 00 00 00 00 00  1b 00 00 00 04 00 00 00  |................|

00000350  40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |@...............|

00000360  80 02 00 00 00 00 00 00  30 00 00 00 00 00 00 00  |........0.......|

00000370  0b 00 00 00 01 00 00 00  08 00 00 00 00 00 00 00  |................|

00000380  18 00 00 00 00 00 00 00  26 00 00 00 01 00 00 00  |........&.......|

00000390  03 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000003a0  7d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |}...............|

000003b0  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

000003c0  00 00 00 00 00 00 00 00  2c 00 00 00 08 00 00 00  |........,.......|

000003d0  03 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000003e0  7d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |}...............|

000003f0  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000400  00 00 00 00 00 00 00 00  31 00 00 00 01 00 00 00  |........1.......|

00000410  02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000420  7d 00 00 00 00 00 00 00  0e 00 00 00 00 00 00 00  |}...............|

00000430  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000440  00 00 00 00 00 00 00 00  39 00 00 00 01 00 00 00  |........9.......|

00000450  30 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |0...............|

00000460  8b 00 00 00 00 00 00 00  2e 00 00 00 00 00 00 00  |................|

00000470  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000480  01 00 00 00 00 00 00 00  42 00 00 00 01 00 00 00  |........B.......|

00000490  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000004a0  b9 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000004b0  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

000004c0  00 00 00 00 00 00 00 00  57 00 00 00 01 00 00 00  |........W.......|

000004d0  02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000004e0  c0 00 00 00 00 00 00 00  38 00 00 00 00 00 00 00  |........8.......|

000004f0  00 00 00 00 00 00 00 00  08 00 00 00 00 00 00 00  |................|

00000500  00 00 00 00 00 00 00 00  52 00 00 00 04 00 00 00  |........R.......|

00000510  40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |@...............|

00000520  b0 02 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|

00000530  0b 00 00 00 08 00 00 00  08 00 00 00 00 00 00 00  |................|

00000540  18 00 00 00 00 00 00 00  11 00 00 00 03 00 00 00  |................|

00000550  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000560  f8 00 00 00 00 00 00 00  61 00 00 00 00 00 00 00  |........a.......|

00000570  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000580  00 00 00 00 00 00 00 00  01 00 00 00 02 00 00 00  |................|

00000590  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000005a0  60 01 00 00 00 00 00 00  08 01 00 00 00 00 00 00  |`...............|

000005b0  0c 00 00 00 09 00 00 00  08 00 00 00 00 00 00 00  |................|

000005c0  18 00 00 00 00 00 00 00  09 00 00 00 03 00 00 00  |................|

000005d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

000005e0  68 02 00 00 00 00 00 00  17 00 00 00 00 00 00 00  |h...............|

000005f0  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|

00000600  00 00 00 00 00 00 00 00                           |........|

00000608



그냥 보지마세요. 머리만 아픕니다. 

아 그냥 기계만 해석할 수 있는 언어구나~ 아시면 됩니다.




링커(Linker)

링커는 이름이 말해주듯 연결해주는 역할을 합니다. 여러개의 오브젝트파일을 하나로 합치거나 라이브러리를 합칠때 링커가 필요하다는 거지요.


우리는 일반적으로 개발할때 협업을 합니다. 그래서 위와 같이 오브젝트 파일(.o)라던가 라이브러리 파일이 여럿 존재할 수 있는데 하나의 소프트웨어를 만들기 위해서는 위의 파일들을 합쳐야하는 거죠. 이해되셨나요?




이제 실행파일을 만들어봅시다.


gcc program.o -o program.exe


그 후 실행을 시키면 


./program.exe

10 + 20 = 30


정상적으로 실행이 되는 것을 확인할 수 있습니다.


뭔가 복잡해보이지만 이러한 과정을 아는 개발자와 모르는 개발자와는 차이가 있다는 것을 알아두세요.


끝!

반응형
블로그 이미지

REAKWON

와나진짜

,

함수(Function)


함수를 중학생때부터 배우죠? 그렇기 때문에 저는 중학교 시절 수학을 포기했습니다. 여러분들은 저보다 뛰어나시니 그렇지는 않았겠죠?


C언어에서 함수는 아주 필수적이라고 할 수 있습니다. 함수에 대해서 간단히 말씀을 드리면 반복되는 코드를 하나로 묶어 필요할때 가져다가 쓴다는 것입니다.


다음의 코드는 어떻게 생각하시나요?

단순히 세 입력의 펙토리얼(!)을 구하여 곱하는 프로그램이지요.




#include <stdio.h>

int main() {
	int fact_a = 1, fact_b = 1, fact_c = 1;
	int a, b, c;
	int i;
	scanf("%d %d %d", &a, &b, &c);
	
	for (i = 1; i <= a; i++)
		fact_a*=i;
	
	for (i = 1; i <= b; i++)
		fact_b *= i;

	for (i = 1; i <= c; i++)
		fact_c *= i;

	printf("%d! * %d! * %d!= %d\n", a, b, c, fact_a*fact_b*fact_c);

	return 0;
}


프로그램이 잘 동작하는지 실행해보세요!

잘 돌아가지요? 문제 없습니다.


하지만 저는 조금 불만인데요. 펙토리얼을 세번 불러오는데, 세번 다 for루프를 돌려야하기 때문에 손가락이 아픈게 그 이유인데요.


만약 10개의 입력으로 들어오고 10개의 펙토리얼의 곱을 구하게 되면 위의 코드에서 for루프를 7개 더 추가해야한다는 것이 매우 불만이지요.


우리가 팩토리얼의 기능을 하는 하나의 코드를 두고 그 코드를 원할때 마다 불러온다면 어떨까요? 그런 역할을 하는 것이 바로 함수입니다.


함수 구현 방식

함수는 어떻게 생겨먹었을까요? 꽤나 간단하게 이해할 수 있을겁니다.

함수의 형태는 이렇습니다.


반환형 함수이름(매개변수1, 매개변수2, ... ){

         //몸체

         //return 반환값

}


o 반환형 : 반환되는 값의 자료형을 의미합니다. 함수에서는 반환되는 값이 없을 수 있는데요. 그 경우 void를 사용합니다. 만약 반환값이 있다면 return에서 값을 반환시켜 주면 됩니다. 그러니 반환값의 자료형과 반환형이 일치해야됩니다.


o 함수이름 : 함수를 불러올때 사용되는 이름입니다. 여러분이 지어주기 나름인데, 이 이름을 보고 사용하는 사람이 "아~ 이런 기능을 하는 함수겠구나!" 라고 알 수 있도록 잘 지어주어야 합니다.


o 매개변수 : 함수에 대한 input이라고 생각하면 됩니다. 이 매개변수를 토대로 함수의 반환값이 달라질 수 있습니다.


기본적인 매개변수의 동작은 전달받은 인자의 값을 복사하는 것입니다. (단, 포인터와 같은 매개변수는 값의 복사가 아닌 참조를 하게 됩니다.)


o 몸체 : 함수가 어떻게 기능을 할지 로직을 구현하는 부분입니다.


return : 반환값을 반환하는 명령입니다. return은 제어문으로 여러개 올 수 있습니다. 단, return은 한번만 진행하므로 만약 if 조건에서 return 문을 썼는데, if 조건에 걸리게 된다면 이 후의 코드를 실행시키지 않고 반환합니다.


혹은 반환형이 void이지만 그 함수를 어떤 조건에서 끝내고 싶다면 반환값없이 그냥 return을 사용해주면 그 즉시 함수를 끝냅니다.




우리는 위의 허접한 코드를 factorial이라는 함수를 만들어 조금 더 간편하게 바꿔볼 생각입니다.

위의 형식 그대로 사용해서 factorial을 구현한다면 이렇게 생겼겠죠?


int factorial(int n) {
	int ret = 1;
	int i;
	for (i = 1; i <= n; i++)
		ret *= i;
	return ret;
}


반환형태는 int형이면서 매개변수는 정수형 n입니다. 비교해보세요. 반환형과 반환값(ret)의 자료형이 일치하는 것을 알 수 있죠?


그 후 메인에서는 이 함수를 호출해서 쓰기만 하면 된답니다.


#include <stdio.h> int factorial(int n) { int ret = 1; int i; for (i = 1; i <= n; i++) ret *= i; return ret; } int main() { int fact_a = 1, fact_b = 1, fact_c = 1; int a, b, c; int i; scanf("%d %d %d", &a, &b, &c); fact_a = factorial(a); fact_b = factorial(b); fact_c = factorial(c); printf("%d! * %d! * %d!= %d\n", a, b, c, fact_a*fact_b*fact_c); return 0; }


어때요? 메인이 훨씬 간결해졌음을 알 수 있습니다.


호출 과정은 다음과 같습니다.



메인 함수를 실행하다가 factorial함수를 만났습니다. 그러면 factorial 함수를 실행시키고 함수가 끝나면 다시 메인함수로 돌아와서 그 전에 실행했던 것을 계속 진행하게 됩니다.



fact_a는 factorial 함수의 반환값이 저장됩니다. 나머지 fact_b, fact_c도 역시 마찬가지구요.


만약 10개의 입력이 주어진다하더라도 factorial만 10번 호출하면 되지요.

(아 물론 이 경우에는 배열과 반복문을 써야하겠지만)




함수 선언

근데 꼭 위에서만 함수를 정의하고 몸체를 구현해야할까요? 그럴필요는 없습니다. 

함수를 메인 아래에서 정의할 수도 있습니다.


하지만 꼭 위에서 함수 선언을 해주어야만 합니다. 왜 그러냐구요?

C언어는 절차지향언어이기 때문에 위에서 아래로 실행하기 때문이지요. 그래서 함수가 밑에 정의되어있는데 메인함수에서 그 함수를 호출한다고 하면 컴파일러는 그 함수를 본적이 없으니까 컴파일 에러를 토하게 됩니다.


위의 코드를 함수의 선언 방식으로 코딩해보도록 하면 다음과 같이 간단하게 바뀝니다.




#include <stdio.h>

int factorial(int);     //함수선언

int main() {
	int fact_a = 1, fact_b = 1, fact_c = 1;
	int a, b, c;
	int i;
	scanf("%d %d %d", &a, &b, &c);
	
	fact_a = factorial(a);
	fact_b = factorial(b);
	fact_c = factorial(c);
	
	printf("%d! * %d! * %d!= %d\n", a, b, c, fact_a*fact_b*fact_c);

	return 0;
}

int factorial(int n) {
	int ret = 1;
	int i;
	for (i = 1; i <= n; i++)
		ret *= i;
	return ret;
}


함수에 밑에 있군요. 메인함수 위의 선언이 있죠?

선언에서는 매개변수의 자료형만 적어주어도 상관없습니다.


재귀함수


함수에서 자신의 함수를 불러오는 것을 바로 재귀함수라고 합니다. factorial함수는 재귀함수로도 구현할 수 있습니다.

int factorial(int n) {
	if (n <= 1) return 1;
	return n*factorial(n - 1);
}

factorial함수에서 factorial함수를 호출하는 것을 볼 수 있지요? 매개변수 n과 다음 factorial(n-1)의 반환값을 곱하는 과정을 반복하고 있습니다. factorial의 매개변수 n은 하나씩 줄어들어 결국에는 1 이하가 될겁니다. 그때 1을 반환하지요.


결국 n * (n-1) * (n-2) * ... * 1이 되어 n!을 구현하는 함수죠.


그림으로 보면 더 이해가 쉽게 될겁니다.






3!을 구하는 과정을 보여줍니다. factorial(3)은 factorial(2)를 호출하고 factorial(2)는 factorial(1)을 호출하는 과정을 보여주고 있습니다.


이때 factorial(1)은 if조건문에 걸려 1을 반환하여 더이상 자신을 호출하지 않습니다. 

*** 이를 기저 사례라고 합니다.


재귀함수는 시스템의 스택을 사용하고 계속 사용할 경우 stack overflow가 발생할 수 있으므로 되도록이면 반복문을 사용하는 것이 좋습니다.


이상으로 함수에 대해서 기본적인 설명을 해봤습니다.


반응형
블로그 이미지

REAKWON

와나진짜

,

스트림

자바에서도 여러 입출력을 지원하지만 이번에 우리의 관심사는 바로 자바에서 제공하는 파일 입출력입니다.


그 전 우리는 스트림에 대한 이야기를 잠깐 간략하게 하고 넘어가겠습니다. 우선 파일에서 입력과 출력이라는 동작을 하려면 파일로 데이터를 전달하거나 파일로부터 전달 받는 길을 열어주어야합니다.


그러한 길을 스트림이라고 하지요.





파일로부터 입력을 받는 스트림을 입력스트림, 출력을 보내는 스트림을 출력스트림이라고 합니다.

그리고 바이너리 형태로 데이터를 입출력하는 스트림을 이진스트림, 문자형태로 입출력하는 스트림을 텍스트스트림이라고 합니다.




스트림을 알았으니 파일입출력을 알아보도록 합시다.


FileReader와 FileWriter


import java.io.*; public class Main { public static void main(String[] args) throws IOException{ File file=new File("test.txt"); if(!file.exists()) file.createNewFile(); FileWriter fw=new FileWriter(file); char []buf= {'m','e','s','s','a','g','e','\r','\n'}; for(int i=0;i<buf.length;i++) fw.write(buf[i]); fw.close(); FileReader fr=new FileReader(file); int EOF=-1; int c; while((c=fr.read())!=EOF) { System.out.print((char)c); } fr.close(); } }


이 코드는 test.txt파일에 "message"라는 문자열을 기록하고 읽어오는 프로그램이에요. 한줄 한줄씩 보도록 하지요.


우선 File 입출력시에는 IOException이 발생할 수 있고 처리가 귀찮으니 저 멀리 보내버리도록 합시다. 가버렷!


일단 file을 열어줘야겠지요? 절대 경로로 지정하지 않는다면 현재 프로젝트 디렉토리에 파일을 엽니다.


하지만 그 파일이 없다면 새로 생성합니다. 

이것을 다음의 라인이 나타냅니다.


File file=new File("test.txt"); if(!file.exists()) file.createNewFile();


그 후 파일에 "message"라는 문자열을 기록합니다. 그때 사용하는 클래스가 바로 FileWriter라는 클래스이지요.

FileWriter의 메소드 write를 통해서 char 배열의 문자열을 하나씩 기록한 후 스트림을 닫습니다.




닫아주어야 파일에 문자열이 입력이 됩니다! 파일을 닫지 않고 파일에 입력하려면 그렇지 않으면 flush함수를 사용하세요. 


FileWriter fw=new FileWriter(file);
char []buf= {'m','e','s','s','a','g','e','\r','\n'};
for(int i=0;i<buf.length;i++)
	fw.write(buf[i]);
fw.close();

이후 한문자씩 읽어오는데 그 역할을 수행하는 클래스가 바로 FileReader입니다. 파일의 끝은 int형의 -1입니다. 그래서 -1을 만날때까지 한문자 한문자 출력합니다.

int형태로 읽어왔으니 char로 바꿔줘야겠지요?


FileReader fr=new FileReader(file);
int EOF=-1;
int c;
while((c=fr.read())!=EOF) {
	System.out.print((char)c);
}
fr.close();


이제 수행을 해보도록 할게요. 어떤 변화가 있는지..


test.txt라는 파일이 생겼네요! 



이것을 까보면!




그안에 우리가 집어넣은 문자열이 존재하는 군요.


eclipse상의 결과 역시 "message"라는 문자열을 출력하는 군요.


파일에 기록한 문자열을 자바프로그램이 읽어온 것이랍니다.



한글자 한글자 읽어오는 것이 여간 불편한 것이 아니죠?

문자열을 사용해서 쓰는 것이 훨씬 더 편할텐데요.

그리고 읽어올때도 배열을 써서 읽어오는 것이 훨씬 편하구요.




그런 방법이 아래에 나와있습니다.




import java.io.*;


public class Main {
	public static void main(String[] args) throws IOException{
		File file=new File("test.txt");
		if(!file.exists())
			file.createNewFile();
		
		FileWriter fw=new FileWriter(file);
		fw.write("Hello, world!!\r\n");
		
	
		fw.close();
		
		FileReader fr=new FileReader(file);
		
		while(true) {
			char []buf=new char[4];
			int ret=fr.read(buf);
			if(ret==-1) break;
			System.out.print(String.valueOf(buf));
		}
		fr.close();
	}
}


FileWriter는 String을 받는 write메소드가 있어서 문자열로 그대로 파일에 기록할 수 있습니다.


중요한건 FileReader인데요. 우선 buf라는 char형 4개에 문자열을 계속 입력받는 거지요. 만약 파일에서 더 읽어올 것이 없다면 -1을 리턴하게 되니까 그 반환형이 -1이면 while루프를 탈출하면 됩니다.


이제 실행 후 파일을 확인해보고 이클립스에서도 확인해 봅시다.





파일에 제대로 적혀있고 이클립스 출력도 이 문자열이 나오는 것을 확인할 수 있죠?



Line 단위 입출력


아직도 불편하긴 합니다. /r/n을 통해서 개행하는 것도 별로구요. 보통 문자 입출력시에는 라인 단위로 입출력을 하기 때문에 라인별로 입력을 할 수 있었으면 좋겠습니다.


그래서 나온것이 버퍼단위의 입출력을 담당하고 있는 BufferedReader, BufferedWriter입니다.



import java.io.*;


public class Main {
	public static void main(String[] args) throws IOException{
		File file=new File("test.txt");
		if(!file.exists())
			file.createNewFile();
		
		BufferedWriter bw=new BufferedWriter(new FileWriter(file));
		
		bw.write("Hello, world!");
		bw.newLine();
		bw.write("Hello, world!!");
		bw.newLine();
		bw.write("Hello, world!!!");
		bw.newLine();
		bw.close();
		
		BufferedReader br=new BufferedReader(new FileReader(file));
		String line=null;
		while((line=br.readLine())!=null)
			System.out.println(line);
			
		br.close();
	}
}


BufferedWriter와 BufferedReader는 버퍼의 사이즈를 지정할 수도 있습니다. 그렇지 않으면 Default 사이즈로 버퍼에 담습니다.


BufferedWriter의 newLine메소드는 개행을 말합니다. 쉽죠? 별거 없어요.


여기서는 Hello, world!를 3라인에 걸쳐 출력합니다. (느낌표 갯수만 다르고요)


이제 BufferedReader를 통해서 읽어옵니다. 

readLine은 String 형태의 문자열을 반환하는데, 만약 더이상 출력할 문자열이 없으면 null을 반환하죠.


그래서 null을 만나게 되면 while루프를 탈출합니다.


이제 실행후 파일과 이클립스화면을 보면 둘의 결과는 같다는 것을 알 수 있습니다.


test.txt




이클립스 실행결과


Hello, world!

Hello, world!!

Hello, world!!!



어떻습니까?


BufferedReader와 BufferedWriter 정말 편리하죠?

이 두 클래스는 조금 빈번하게 쓰입니다. 기억해두세요.


아직 파일입출력에 대해서는 더 할 이야기가 나왔습니다. 나중에 더 이야기 해보도록 하지요.

반응형
블로그 이미지

REAKWON

와나진짜

,