한국에서는 PS에 관한 서비스가 굉장히 많이 마련되어있습니다. 코드업, BOJ, koistudy, JUNGOL, oj.uz 등 수많은 한국 온라인저지(OJ) 사이트가 존재합니다. 대부분의 OJ에서는 각각의 문제에서 실행시간 등을 기준으로 사용자를 정렬하여 보여주는 ‘순위’ 페이지가 존재합니다. 이러한 순위에 도전함에 있어 필요한 상당히 괴상한 종류의 지적유희, 숏코딩을 소개하고자 합니다.
숏코딩의 목적은 단순합니다.
“실행시간도, 메모리 사용량도 제한 안에만 들어가면 상관 없으니 무작정 짧은 코드를 짜보자!”
이 직관적이고 명료한 목표를 달성하기 위해서는, 비직관적이고 괴상한, 웬만해서는 쓰지 않을 법한 발상과 프로그래밍 언어에 대한 깊은 이해가 필요합니다.
이번 글에서는 C언어의 GCC 컴파일러를 사용하는 숏코딩 기법들을 몇 가지 소개하고자 합니다.
일반적인 프로그래밍과 다르게, 숏코딩에서는 가독성을 신경쓰지 않습니다. 여러 기법을 적용하며 코드의 가독성이 떨어지는 것도 있지만, 인덴트 등 코드 스타일을 전혀 신경쓰지 않아 코드를 유지보수하는 것을 매우 힘들게 만듭니다. 또한 몇몇 성능 저하 이슈도 발생할 가능성이 있습니다.
숏코딩은 오직 지적유희로만, 개인적인 도전으로만 즐기도록 합시다.
1. C(gcc)는 표준헤더를 #include 없이 사용할 수 있다.
우선, 많은 온라인저지의 C언어 컴파일러는 gcc 컴파일러임을 기억해두기로 합시다. 몇가지 팁들은 비표준이나 gcc에서 허용되는 문법, 혹은 표준에서 미정의된 동작이나 gcc의 구현 방식에서 성립하는 동작을 사용합니다.
C언어를 작성하면서, 간단한 코드의 경우 숏코딩을 방해하는 장애물은 #include문입니다. 일반적인 경우 생략이 불가능하며 한 줄에 여러 코드를 작성할 수도 없어 개행문자까지 코드의 많은 바이트 수를 잡아먹는 주범입니다.
하지만 gcc 컴파일러는 이를 생략하여도 적절히 반영해주므로, 숏코딩을 하는 입장에서는 컴파일러를 선택할 수 있다면 gcc를 선택하는 것이 대체로 유리합니다.
이 팁을 사용하여 Hello!를 출력하는 C언어 프로그램을 작성해봅시다. 기존에는 헤더를 생략하지 않았으므로 최대한 짧게 작성해도
#include<stdio.h>
int main(){puts("Hello!");}
가 한계겠군요. 45바이트입니다. 반면에 헤더를 생략한다면,
int main(){puts("Hello!");}
27바이트! 무려 18바이트를 줄일 수 있습니다.
2. C(gcc)는 함수의 반환형 혹은 전역변수의 자료형을 명시하지 않으면 암묵적으로 int형을 반환하는 것으로 취급된다.
말 그대로 입니다. 1.의 Hello!를 출력하는 C언어 프로그램 코드에서 int main()를 main()으로 써도 된다는 얘기입니다. 그렇다면 “int “가 생략되므로 총 4바이트를 절약할 수 있죠. 유사하게 전역변수의 자료형을 생략하면 int형으로 인식되게 됩니다.
main(){puts("Hello!");}
3. 다양한 입출력함수를 사용하라.
1.과 2.에서 은근슬쩍 사용한 팁입니다만, 상황에 맞게 다양한 입출력함수를 사용하는 것은 도움이 됩니다. 일반적으로 C언어 프로그래밍을 할 때는 printf와 scanf만을 쓰게 됩니다만, 다음과 같은 입출력함수 사용을 고려해보면 코드 길이를 줄일 수 있는 경우가 종종 있습니다.
getchar() : 문자 하나를 입력받음
putchar(ch) : 문자 ch를 출력함
gets(char* str) : 한 줄 입력받으며, str에 입력받은 문자열을 저장한다.
puts(char* str) : str에 저장된 문자열을 출력한다. 문자열 끝에 개행을 붙이는 것이 특징.
4. 삼항연산자를 적극 사용하라.
삼항연산자는 일부 경우에 if문을 대신할 수 있는 강력한 도구가 됩니다. a?b:c 꼴에서 a가 참이면 c는 실행되지 않고, a가 거짓이면 b는 실행되지 않으므로 특정 경우 if문을 완벽히 대체합니다.
a;main(){scanf("%d",&a);if(a)puts("1");else puts("0");}
간단히 if-else를 사용하여 써본 코드입니다. 55바이트로 꽤나 짧은 편이긴 합니다만, 삼항연산자를 사용하면 더 줄일 수 있습니다.
a;main(){scanf("%d",&a);puts(a?"1":"2");}
41바이트로, 코드 길이를 크게 줄일 수 있습니다.
5. return문은 사치!
숏코딩을 조금씩 익히다 보니, return문 정말 길다고 느껴지지 않나요? 숏코딩으로 해결을 시도할만한 문제들은 대부분 단순하고, 함수를 만들더라도 함수의 호출횟수가 1번인 경우가 존재합니다. 그런 경우에 함수의 반환값을 return을 쓰지 않고 반환하는 일회성 기법이 있습니다.
C언어에서 return문이 호출되면, 해당 값을 eax 레지스터라는 곳에 값을 쌓아둡니다. 이 eax 레지스터는 이외에도 다양한 용도로 사용되는 곳인데, 대표적으로 덧셈 연산을 할 때 대상 값을 담아두는 용도로 쓰입니다. 그래서 사용할 수 있는 최적화 기법은 다음과 같은 예시가 있습니다.
a;f(){++a;}
이 코드는 a를 1 증가시키는 함수를 구현한 것입니다만, gcc에서는 eax 레지스터에 1이 쌓이기 때문에 1을 반환한다는 특징이 추가됩니다.
6. 인덴트, 공백? 그게 뭐죠? 먹는건가요?
보통 코딩을 할 때는 깔끔하게 인덴트를 넣어서 가독성 좋은 코드를 작성합니다만, C언어에서 인덴트가 필수는 아니므로 숏코딩에서는 모두 지워주면 코드가 굉장히 짧아질 수 있습니다.
하지만 코드를 작성하는 본인도 코드를 알아보지 못하는 경우는 피해야 하니, 보통 코드를 다 작성한 후에 공백과 인덴트를 지우는 편입니다.
C언어 숏코딩은 즐거운 지적유희 중 하나입니다. 하지만 너무 심취하면 정상적인 코드 작성에 방해가 될 수도 있고, 실은 가독성을 해치는 나쁜 코딩 습관으로 보일 수도 있습니다. 숏코딩이라는 지적유희를 즐기되, 적절한 선을 지켜 멋진 코드를 작성하는 프로그래머가 되도록 합시다.