C 언어 다시 파고들기
C 언어는 흔히 많은 언어의 기초라고 한다. 실제로 C 언어가 많은 언어의 기반이 되기도 했고, 영향을 주기도 했기 때문이다.
C 계열이라고 불리는 C++/C#이나, Java, Python 등을 다루기 위해 C언어를 배우거나, 그 반대로 배우기도 한다. 그리고 C언어의 꽃이라고 불리는 메모리 관리와 포인터의 복잡함에 익숙해질 때쯤, 우리는 종종 가장 기본적인 것들을 잊어버리기도 한다.
또한 흔히 IT계열의 운전면허증이라고 불리는 정보처리기사를 위해 공부하는 사람들도 많고, 이 정처기 는 최근 코딩 문제의 비율과 난이도를 올리고 있다. printf()의 반환 값을 물어본 적도 있을 정도다.
이번 포스트에서는 C언어를 어느 정도 경험해 본 사람들과 경험하고자 하는 사람들에게
int main()으로 시작해 printf로 끝나는, 너무나도 당연하게 사용했던 문법들 속에 숨겨진 것들을 파고 들어본 적이 있는가?라는 질문을 던져보고, 파고들어 본다.
1. 모든 시작의 중심. main함수
C언어의 시작은 언제나 main()이다.
main함수는 항상 int를 반환 값으로 주었고, 인자를 넣는 소괄호(())에는 void를 넣었다.
여기에서 main함수의 형태와 반환 값의 진짜 의미는 무엇일까?
1.1. main함수와 매개변수
int main(void) vs int main(int argc, char *argv[])많은 입문서에서는 int main(void)로 C언어의 시작을 알린다. 이는 프로그램이 외부로부터 어떠한 인자(값, 매개변수)도 받지 않는다는 명시적 표현이다.
하지만 다음과 같이 터미널에서 프로그램을 실행할 때, 인자를 넘겨줘야 한다면?
이때 사용되는 것이 바로 argc와 argv이다.
int argc- Argument Count. 프로그램을 실행할 때 전달된 인자의 개수를 저장한다.
- 흥미로운 점은, 실행되는 프로그램의 이름도 인자로 취급되어 argc는 최소 1이다.
char *argv[]- Argument Vector. 전달된 인자의 실제 내용(문자열)이 저장된 포인터 배열이다.
argv[0]는 프로그램의 이름,argv[1]부터는 순서대로 전달된 인자들이다.
C 언어의 예제 코드를 짜고, 실행해보자.
결과 파일이 myProgram이라면, 다음과 같이 인자를 넣어보자.
1.2. return 0;의 의미
main 함수의 끝에 습관처럼 붙이는 return 0;. 이는 단순히 함수를 종료하는 것을 넘어, **"이 프로그램은 성공적으로 종료되었습니다"**라고 운영체제(OS)에게 알리는 신호이다.
그렇다면 왜 0을 반환하는 것일까?
C 표준에서는 <stdlib.h> 헤더에 2개의 매크로를 정의해 두었다.
EXIT_SUCCESS: 프로그램이 성공적으로 종료됨 (일반적으로 0)EXIT_FAILURE: 프로그램이 실패하고 종료됨 (일반적으로 1)
우리는 return EXIT_SUCCESS;를 사용하는 대신, 일반적으로 EXIT_SUCCESS가 0이라고 인식하고 있으므로, return 0;라고 적는 것이다.
return EXIT_SUCCESS;를 사용하는 것이 좀 더 명시적이고 이식성이 높은 코드가 될 수 있다.2. 세상과의 소통: Hello World!
printf와 scanf는 C언어의 가장 기본적인 입출력 함수이자 우리가 처음 접하게 되는 함수들이다.
2.1. printf()
아마 프로그래밍을 배웠다면, 다음 코드를 한 번도 보지 못한 사람은 없을 것이다.
무엇을 하든, 아마 가장 많이 사용한 함수는 printf이지 않을까 싶다.
여기에서 한가지 궁금증을 품어볼 수 있는데, printf()는 함수이다. 함수는
일반적으로, 값을 입력받은 뒤 무언가를 반환한다.
printf()의 반환 값이 void가 아니라는 것을 안다면, 대체 무엇을 반환하는지 궁금증을 품을 수밖에 없다.
결과:
혹시 눈치챘는지 모르겠다.
굉장히 흥미로운 사실인데, printf()는 출력에 성공한 문자의 byte 수를 정수형의 양의 정수로 반환하고, 만약 출력 중 오류가 발생하면 음수를 반환하여 에러가 발생했음을 알린다.
printf가 글자 수가 아닌 바이트(byte) 수를 반환하기 때문이다. 현대에 널리 쓰이는 UTF-8 인코딩에서는 한글 한 글자가 3byte를 차지하므로 5 * 3 = 15가 반환된다. 반면, 과거에 사용되던 EUC-KR 인코딩 환경이라면 한글이 2byte이므로 10이 반환될 수 있다.이게 대체 어디에 쓰일 수 있냐고 묻는다면, 꽤나 유용하다. C 언어는 터미널(CMD)에서 글자를 작성하는 커서의 위치를 직접 조작할 수 있는데, 한참 전에 출력된 문자열 뒤에 출력한다거나, 작성했던 글자의 일부분을 지우고 싶다면, 이만큼 유용할 수 없다.
2.2. scanf()
scanf()도 상당히 많은 예제에서 사용했을 것이다. switch로 번호 고르기, 숫자 글자 입력하고 printf로 맞다 아니다 등등...
scanf는 사용자의 입력을 파싱하여 변수에 저장하는 역할을 한다. 그리고 scanf()도 함수다. 그럼 scanf()의 반환 값은 무엇인가?
바로 성공적으로 값을 할당한 변수의 개수이다. 이 반환 값을 확인하는 것은 매우 중요한 예외 처리 기법이다. 예를 들어, 정수를 두 개 입력 받고자 한다면,
이와 같이 코딩해볼 수 있다.
만약 사용자가 10 abc라고 입력한다면, result에는 1이 저장될 것이고, 제대로 입력되지 않았다는 문구를 띄운다. 여기서 result를 확인하지 않는다면, b에는 정상적이지 않은 이상한 값(쓰레기 값)이 저장된 채로 프로그램이 실행된다.
scanf()는 버퍼 overflow나 입력 버퍼에 남는 \n 문제 등 다루기가 까다로우므로, fgets()를 사용하는 것이 훨씬 안전하다.2.2.1. fgets() 살짝 맛보기
fgets()는 파일을 읽는 데에 많이 사용되지만, stdin을 사용해 사용자의 입력을 받을 수도 있다. fgets()로 입력을 받으면, 버퍼 오버플로우를 방지할 수 있어 안전하고 예측 가능한 코드를 작성할 수 있게된다.
fgets로 사용자 입력 받기
fgets함수는 세 개의 인자를 받는다.- 버퍼: 입력받은 문자열을 저장할 메모리 공간(char 배열)
- 버퍼 크기: 최대로 읽어들일 문자의 개수
- 입력 스트림: 사용자 키보드 입력을 의미하는
stdin
fgets() 주의점: 개행문자(\n)
fgets는 사용자가 Enter키를 누를 때 입력되는 개행문자(\n)까지 버퍼에 저장하기 때문에, 개행 문자가 필요하지 않다면 문자열의 끝에 있는 개행 문자를 찾아 NULL문자(\0)로 바꿔주는 작업이 필요하다3. 데이터 타입의 진짜 의미
int는 4바이트, char는 1바이트... 우리는 데이터 타입의 크기를 외우곤 한다. 하지만 C 표준에서는 데이터 타입의 정확한 크기를 명시하지 않는다. 최소한의 범위만을 규정할 뿐이다.
sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)특정 시스템에서 자료형이 차지하는 실제 메모리의 크기를 확인하려면 sizeof 연산자를 사용할 필요가 있다.
sizeof의 반환 값은 size_t이며, %zu가 정확하다.위의 예시에서 알 수 있는 점은 우리가 흔히 외우는 int, char... 등의 크기를 고정적이지 않다는 사실이다. 물론 일반적으로 대부분의 시스템은 그러하겠으나, 더 나은 프로그래머를 지향한다면, 분명 그렇지 않은 시스템이 있음을 고려할 수 있어야 한다.
우리가 알아야 할 것은 int, char, float과 같은 타입은 해당 시스템에서 이 변수가 차지하고자 하는 메모리의 크기를 명시한다는 사실이다.
4. 마무리 하며...
이번 글에서는 많은 사람들이 C언어를 자연스럽게 여기고 사용하던 main, 입출력함수, 그리고 기본 데이터 타입에서 놓칠 수 있는 부분을 다뤄보았다.
이번 글을 정리해보자면
main함수의 인자argc,argv를 통해 외부의 인자를 받는다.return 0;는 OS에게 프로그램의 종료 결과를 통보한다.printf와scanf는 각각 출력 완료된 바이트 수, 입력 완료된 값 수를 리턴한다.- 입출력 함수의 반환 값을 통해 코드의 안정성과 디버깅 효율을 높일 수 있다.
- 데이터 타입의 크기는 절대적이지 않다.
C언어는 파면 팔수록 새로운 면모를 보여주는 low-level의 언어이다. 복잡한 포인터와 구조체도 좋지만, 이렇게 기본적인 부분을 한 번 점검해 보는 것도 좋다.
필자도 아직 학생으로서 배우고 있는 입장에서, 프로그래머에게 중요한 부분 중 하나는 호기심이라고 생각한다. 당연하다고 생각하는 것에도 호기심과 궁금증을 가지고, 연구하고 파보는 것도 큰 도움이 되지 않을까라는 생각이다.