printf() 함수와는 다르게 scanf()에서는 변수에 &(앰퍼샌드) 연산자를 이용하여 변수에 값을 대입한다.
하지만 &(앰퍼샌드) 연산자를 사용하는 이유에 대해서는 의문은 가지고 있지만, 정작 대다수가 scanf() 함수를 사용하면서도 동작에 대한 소스 코드를 분석할 방법이 없어 scanf()이기 때문에 &(앰퍼샌드)를 사용하는 것이라는 공식처럼 여기고 넘어갈 것이다.
이번 포스팅에서는 scanf()에 대한 라이브러리 분석을 통해 &(앰퍼샌드) 연산자를 쓰는 이유에 대하여 분석해본다.
해당 기본 라이브러리는 MinGW를 기준으로 분석을 진행하여 scanf() 함수가 어떠한 경로를 통해 구동되는지 흐름까지 설명할 것이며, 자세한 동작 로직 분석은 오픈 소스를 통해 직접 확인해보기를 바란다.
아래와 같이 scanf() 함수를 호출하여 temp 배열에 입력 값을 넣은 예제 코드가 있다.
scanf() 함수를 사용하기 위해서는 정의된 라이브러리인 stdio.h 헤더 파일을 include 해줘야 한다. stdio.h는 Standard Input/Output의 약자로 개발 도구에 따라서는 stdio.h 헤더 파일을 include 하지 않아도 자동으로 삽입하여 컴파일 시켜준다.
stdio.h 파일을 열어 scanf() 함수의 항목을 보면 다음과 같은 코드로 되어 있다.
기본적인 C 언어 서적에서는 잘 언급되지 않는 register, 가변인자 등등을 사용하여 구현된 것을 확인할 수 있는데, 이 코드에서 핵심은 __retval = __mingw_vscanf( __stream, __format, __local_argv ); 이다. __mingw_vscanf() 함수에 대한 정보는 헤더 파일을 검색해도 나오지 않으며, 이 함수를 확인하기 위해서는 MinGW 오픈소스 코드를 찾아서 확인해봐야 한다. 소스는 아래의 사이트에서 얻을 수 있다.
MinGW Git : https://github.com/Alexpux/mingw-w64
소스를 받아 압축을 풀고 __mingw_vscanf() 함수를 검색하면, 다음과 같이 mingw_scanf.c 소스 코드에 __mingw_vscanf()가 구현된 것을 확인할 수 있다.
__mingw_vscanf() 함수는 다시 __mingw_vfscanf() 함수를 호출하여 결과를 return 한다. __mingw_vfscanf() 함수를 찾아가보자. __mingw_vfscanf() 함수는 mingw_vfscanf.c 소스 파일에 구현되어 있으며, 코드는 아래와 같다.
__mingw_vfscanf() 함수는 다시 __mingw_sformat() 함수를 호출하게 되며, 최종적으로는 __mingw_sformat() 함수를 이용하여, 우리가 알고 있는 scanf()의 서식 문자에 대한 처리를 하는 수행한다. 아래 그림은 서식 문자 중, 자주 자용되는 %d, %i, %o, %p, &u, %x, %X의 파싱 과정의 간단한 예시 코드이다.
여기서 우리가 사용한 %s 서식 문자를 처리하는 로직을 따라가보자. 아래 그림과 같이 in_ch() 함수를 사용하는 것을 확인할 수 있다.
in_ch() 함수의 경우 아래와 같이 최종적으로 가변인자로 받은 값들을 getc() 함수를 이용하여, 입력된 버퍼를 넣는다.
결과적으로, scanf() 함수에서 넘겨 받는 가변인자들의 변수 주소들을 이용하여 처리하기 때문에 &(앰퍼샌드)의 연산자를 이용하여 선언된 변수의 주소값을 넘겨줘야 정상적인 처리가 가능함을 볼 수 있다.
지금까지 분석된 오픈소스 코드를 전부 가지고 와서 나만의 scanf()를 만들고 테스트를 해보자.
위에서 설명한 소스 코드들에서 필요한 함수들(__mingw_vscanf(), __mingw_vscanf(), __mingw_sformat() 등등..)을 모두 긁어온다.
예를 들어, 나의 경우 문자열을 받을 때 쓰는 서식 문자가 '%s'로 하는 것이 마음에 들지 않기 때문에, 이를 scanf()에서 사용하지 않은 서식 문자 중 하나인 '%b'로 수정하여 문자열을 입력 받을 것이다.
아래는 일부 함수명이 헤더 파일로 정의되어 겹치는 부분이 있어, 일부 함수명을 변경하고, 나만의 scanf() 함수의 이름을 programist_input() 함수로 명명하였다.
그리고, 위에서 로직을 설명한 것처럼 문자열에 대한 서식 문자가 '%s'가 아닌 '%b'로 변경하여 컴파일을 하였다.
아래 그림과 같이 scanf() 함수를 사용하지 않은 채, scanf() 함수에서 사용하는 로직을 그대로 가져와서 임의의 scanf()와 동일한 동작을 하는 함수를 만들었으며, 프로그래머가 원한다면 다음과 같이 scanf() 유사한 함수를 프로그래머 입맛대로 수정해서 사용해도 될 것이다.
해당 테스트 코드는 참고용으로 업로드하였다.
프로그래밍 언어를 공부할 때, 문법을 배우고 직접 코드를 작성하는 것도 좋은 방법이지만, 다음과 같이 라이브러리에 대한 동작 방식과 원리를 파악하기 위해 분석을 하다보면 직접 코드를 작성하는 것보다 지금처럼 더 큰 경험을 얻을 수 있다는 것을 염두해야 할 것이다.
'Programming > C' 카테고리의 다른 글
C언어 배열([])과 포인터(*()) 연산자의 교환법칙 (1) | 2017.10.06 |
---|---|
C 언어 포인터 변수 자료형이 필요한 이유 (1) | 2017.09.16 |
C 언어 모든 포인터 변수 자료형이 똑같은 크기를 가지는 이유 (0) | 2017.09.16 |
C 언어 Call-by-Value(값에 의한 호출) & Call-by-Reference(참조에 의한 호출)의 이해 (1) | 2017.09.16 |
C 언어 포인터의 기초 (0) | 2017.09.16 |