재귀함수에 대한 질문입니다.

viper9의 이미지

초보적인 질문인거 같아서 망설이다가 글 올려봅니다.

얼마전에 면접을 보다가 재귀함수에 대해 질문하셨는데 답변을 잘 못하겠더라구요. 검색엔진을 찾아봐도 원하는 답변이 없어서 여기에 질문을 올려봅니다.

재귀함수에 대해 물어보시길래,

예를 들어, 1~n까지 팩토리얼을 구하는 경우에, 재귀함수로도 구할 수 있고 for 루프를 통해서도 구할 수 있습니다.

직접 만들어본 결과, 동일한 기능을 하는 경우에는 재귀함수보다 for 루프를 이용할 때가 속도는 더 빨랐습니다.

그렇게 답했더니, 그럼 재귀함수를 써야할 이유는 무엇일까요?

그래서 재사용성과 코드의 길이, 유지보수에서 더 이득이다. 라고 답했는데 그럼 그냥 for 루프를 쓰는 함수를 하나 만들어서 쓰는 것과 무슨 차이인가? 라고 물으시더라구요.

생각해보니 그것도 맞는 말이어서 대답을 못했습니다.

곰곰히 생각해보니 제가 코드를 만들 때는 재귀함수를 썼던적이 거의 없던 것 같습니다. 스도쿠를 만들려고 시도할 때 썼던 것 빼구요.

대부분은 for, if 문으로 해결했는데 코드가 짧아진다는 것 말고는 딱히 더 장점이 없다고 느꼈습니다. 라고 대답했더니 그럼 왜 쓸까요...라고 하시더라구요.

여기저기 면접을 다니며 제대로 답하지 못했던 것들을 정리해서 다시 공부하고 있는 중입니다.

장점이 없는데 재귀함수가 쓰이는 것도 아닐 것 같은데 검색엔진을 찾아봐도 재귀함수의 용도, 써야만하는 이유에 대해서는 찾을 수가 없어서 답답하네요.

관련된 링크라도 주시면 정말 감사드립니다.

(아 그리고 직접 만들어서 테스트해봤을 때 재귀함수보다 for 루프가 더 빨랐는데 이 이유가 함수의 호출과 반복에서도 비용이 들기 때문이라고 이해하고 있습니다. 이게 정확히 맞는 것인지도 잘 모르겠네요.)

snowall의 이미지

가령, 합병정렬을 반복문으로 구현하려면 꽤 곤란할 것 같아보이는군요. 못할건 없겠지만요.

물론 정렬을 굳이 합병정렬로만 구현할 이유도 없기는 해요.

피할 수 있을때 즐겨라! http://melotopia.net/b

unipro의 이미지

작성한 코드가 정확히 동작하는 것을 증명하는 것은 어렵습니다.
재귀 함수로 작성한 경우는 이것이 쉽다고 알고 있습니다.
수학적으로 정의한 규칙을 그대로 코드로 옮기면 재귀 형태의 함수가 되기 때문입니다.

내 블로그: http://unipro.tistory.com

나그네나그네의 이미지

좋아요 하나 누르고 갑니다 :)

익명 사용자의 이미지

이러한 경우가 있습니다

팩토리얼에서도 있는 경우인데, 아마 19의 팩토리얼밖에 구하지 못할겁니다
그러나 , 재귀함수를 쓸 경우 더 높은 자릿수를 구하거나,, 이런 이득이 있을수 있겠죠?

qiiiiiiiip의 이미지

점화식 스타일의 알고리즘에 대해서는 ( A(n) = f( A(n-1) )

재귀함수가 직관적이라, 알고리즘을 코드로 옮기기가 쉽습니다..

즉 빠른 구현이 가능하다는 것이 장점이죠..

일단 동작확인하고, 최적화 단계에서 다시 루프로 변환하는 경우가 많죠..

february28의 이미지

어느정도 제귀호출에 익숙해지면, 그것이 코드를 직관적으로 보는데 더 쉽기때문이지 않을까라는것이 하나의 이유가 되지 않을까 싶네요...
몇자 보지도않고 먼지 쌓여가고있는 sicp에서도 반복스타일이 좀더 어렵더라구요...
근데, 이런식으로 솔직히 예기하면 욕들어 먹을듯..ㅋㅋ

--------------------------------------------------------------------------------
open source, open teaching, 천기누설이 꿈~ 은 개뿔...
--------------------------------------------------------------------------------

blueiur의 이미지


알고리즘을 기술한 그대로 코드로 표현된다는 점입니다.
점화식 스타일이나 분할정복 모두 자연스럽게 표현됩니다.

당연히 가독성이 높아집니다.

일반적으로 재귀로 구한 코드는 스택을 소비하기 때문에 루프보다 느립니다.
(꼬리재귀 등으로 회피할 수 있습니다)

winner의 이미지

QuickSort 나 하노이의 탑으로 구현하기 매우 어렵습니다.

익명 사용자의 이미지

알고리듬에서 다루는 데이터의 구조를 생각해 보면 리커전이 안쓰이는 곳이 별로 없다는 것이 새삼 느껴집니다. 리스트, 트리, 그래프 등등의 모든 데이터 구조는 리커시브하게 정의됩니다. (그래프도 정의에 리스트가 사용되지요) 이렇게 정의된 객체를 효율적으로 다루기 위해선 어떤 알고리듬이 효율적일까요?
이렇게 볼때, 리커시브하게 정의되지 않은 객체가 얼마나 될까요?
실제 문제를 풀때도 문제를 정의할 객체는 거의 리커시브하게 정의되는 것이 대부분이 아닐까요?
실제로 모든 자연상의 대상체들도 거의 리커시브하게 되어있다는 것을 부인할 수는 없는 것 같습니다. 수는 말할 것도 없고요.
스도쿠를 풀때도 리커전이 사용되는 건 게임 서치트리가 말그대로 트리이기 때문이며,
sicp 에서 리커전이 기본 사고 방식인건 객체의 기본적 데이터 구조가 리스트이기 때문입니다.
(
pair := lambda x . lambda y . lambda z . z x y,
first := lambda p . p ( lambda x. lambda y . x ),
second := lambda p . p ( lambda x . lambda y . y ),

cons := pair,

Y := lambda g . ( lambda x . g ( x x ) ) ( lambda x . g ( x x ) )
제 개인적인 경험으론 'Little Lisper'가 이러한 사고를 훈련하는데 크게 도움을 주는 것 같습니다.)

리스트, 트리, 그래프등의 데이터 구조의 사용을 제외하고 만들 수 있는 프로그램이 몇이나 될까요?

익명 사용자의 이미지

재귀 호출이 중요한 이유는 크게 봐서 두 가지 입니다.

한 가지는 이미 여러분들이 지적하셨듯이 알고리즘 자체가 재귀적인 표현이 자연스러운 경우입니다.

다른 한 가지는 재귀 호출은 '변수 사용'을 줄여줄 수 있다는 겁니다. (아래에 있는 두 코드를 참고하세요.)
좀 추상적으로 말하자면 mutable state가 취할 수 있는 가능한 경우의 수를 줄여줍니다.
결과적으로 프로그램에 오류가 생길 (즉, 잘못된 state로 전이할) 가능성이 줄어들고, 프로그램이 맞다는 것을 확인(특수한 경우에는 증명)하기가 쉬워집니다.

물론 단순한 경우에는 오히려 재귀 호출이 직관적으로 이해하기 더 어려울 수도 있습니다.
하지만 프로그램이 복잡해지면 mutable state를 가능한 한 피하는 것이 오류 없는 프로그램을 짜는 데에 중요한 원칙이 될 수 밖에 없습니다.

mutable state를 가능한 한 피하는 것은 변수의 수를 줄이는 것과 변수가 가질 수 있는 값의 종류 또는 범위를 정확히 제한하는 것이라고 생각하면 됩니다.
변수의 수를 줄이는 것은 재귀 호출이 도와주고, 변수가 가질 수 있는 값의 종류 또는 범위를 정확히 제한하는 것은 type system이 도와줍니다.

#프로그램 1

int sum = 0;
for(int i = 0; i <= 100; ++i) {
sum += i;
}
printf("%d\n", sum);

#프로그램 2

int sum(const int x, const int acc) {
if(x > 100) return acc;
else return sum(x + 1, x + acc);
}

printf("%d\n", sum(0, 0));

둘 다 0부터 100까지의 합을 구하는 프로그램입니다.
그런데 프로그램1에는 변수가 두 개 있고, 프로그램2에는 변수가 하나도 없습니다. (프로그램2에서 x와 acc는 변수가 아닙니다. 값이 바뀔 수 없습니다.)
그리고 이 경우에는 tail recursion이기 때문에 컴파일러가 똑똑하다면 루프를 사용하는 경우와 동일한 성능을 보장할 수 있고 stack overflow도 없습니다.
재귀에 익숙하지 않은 분이 보기에는 프로그램2가 별로 직관적이지 않을 겁니다만
이런 패턴은 흔히 쓰이는 일반적인 패턴(accumulator pattern)이기 때문에 약간만 익숙해지면 자연스럽게 읽힙니다.

oosap의 이미지

와우! 명료한 설명입니다. 제가 질문자는 아니었지만 감사합니다.

컴파일러가 재귀호출의 문제점을 해결해줄 수 있다는 이야기가 중요한 것 같습니다. 재귀함수를 써도 괜찮은 중요한 근거가 될 수 있을 것 같네요.. 어떤 컴파일러가 이렇게 똑똑한지가 아직 숙제가 되겠지만요..

예전에 함수형 언어의 특징이 '사이드 이펙트가 없다는 것' 이라고 어떤 분께서 답글로 알려주신 적이 있었습니다. 오늘 말씀하신 재귀적인 코드가 mutable state 를 줄일 수 있다는 이야기와 일맥상통하는 느낌이 듧니다.

감사합니다.

Thanks for being one of those who care for people and mankind.
I'd like to be one of those as well.

yielding의 이미지

+2

Life rushes on, we are distracted

cleol의 이미지

+3

oosap의 이미지

익명님의 명료한 답글을 보고 추가 질문을 드려봅니다.

질문 #1 : 함수형 언어가 점점 뜨는 것과 컴파일러가 재귀적인 호출을 최적화 해내게 되었다는(?) 것과 관련성이 있을까... 궁금합니다.

이 쓰레드에서 여기까지의 답글들을 보면서 결국 드는 생각은 '관건은 컴파일러에 대해서 잘 알아야 한다는 것' 입니다.

질문 #2 : 사용할 컴파일러가 재귀 호출을 최적화해서 반복문화 할 수 있는지 알아보는 것도 중요할 것 같습니다. 최적화가 되는 재귀 호출과 되지 않는 재귀 호출을 구분해서 최적화가 될 수 있도록 재귀 함수를 작성해야 겠다... 는 생각이 들고요.. 흔히 쓰이는 컴파일러들이 어떤 재귀호출을 반복문처럼 최적화를 해 내는지가 궁금해집니다..

감사합니다.

(추가 질문들 이지만 원래 쓰레드와 관련성이 높아 보여서 이어서 질문 올려봅니다.)

Thanks for being one of those who care for people and mankind.
I'd like to be one of those as well.

익명 사용자의 이미지

> 함수형 언어가 점점 뜨는 것과 컴파일러가 재귀적인 호출을 최적화 해내게 되었다는(?) 것과 관련성이 있을까... 궁금합니다.

아마도 그렇지 않을겁니다. 이른바 함수형 언어들은 대부분 처음 세상에 나올 때부터 재귀호출에 대한 최적화를 가지고 나왔습니다.
고로 그 녀석들은 처음 나올 때부터 적어도 재귀호출에 관한 한 똑똑했습니다.
(underlying platform의 특성상 그렇지 않은 경우도 있기는 합니다. 예를 들어 jvm에서 돌아가는 함수형 언어들 중에는 mutual tail call optimization을 하지 못하는 언어도 있습니다.)

> 흔히 쓰이는 컴파일러들이 어떤 재귀호출을 반복문처럼 최적화를 해 내는지가 궁금해집니다..

가장 중요하고 기본적인 최적화는 tail call optimization(TCO) 입니다. 재귀 함수의 가장 마지막 동작이 자기 자신을 호출하는 경우를 말합니다.
이런 경우에는 현재의 stack frame을 굳이 유지할 필요가 없기 때문에 그냥 그 stack frame을 재사용하면 됩니다.
자기를 다시 부를 때마다 stack frame을 새로 쌓을 필요가 없는거지요. 결과적으로 loop와 마찬가지고 동작하게됩니다.
하지만 tail call이 아닌 경우, 즉 자기를 다시 호출한 이후에 뭔가 다른 일을 더 해야한다면 stack frame을 유지하면서 자기 자신에 대한 호출이 리턴할 때가지 기다려야 합니다.
stack frame을 재사용할 수 없는거지요. 예를 들어 위의 0부터 100까지 더하는 프로그램을 다음과 같이 재귀로 짤 수 있습니다.

int sum(const int x) {
if(x == 100) return x;
else return x + sum(x + 1);
}

이 경우에는 마지막 라인에서 sum(x + 1) 가 리턴한 다음에 x 에 더해야 하기 때문에 x의 값을 계속 유지해야 합니다. 고로 stack frame을 재사용할 수 없고 계속 쌓아가야만 합니다.
이를 피하기 위해서 함수에 인자를 하나 더 추가해서 (보통 accumulator라고 부릅니다.) tail call로 만든 것이 저~기 위에 예로 든 구현입니다.
언어에 따라서는 겉보기에는 tail call이지만 실재로는 아닌 경우가 있으니 주의해야 합니다.
예를 들어 C++에서는 코드 상으로는 가장 마지막 표현이 자기 자신에 대한 호출이지만, local object의 destructor 때문에 실재로는 그렇지 않는 경우가 있으니 주의해야 합니다.

사실 함수가 마지막으로 하는 일이 자기 자신이 아니라 다른 함수에 대한 호출이어도 tail call입니다.
예를 들어 두 함수가 마지막에 서로를 호출하는 mutual recursion도 TCO를 할 수 있습니다.

언어에 따라서 이런 종류의 최적화를 제공하는 정도가 다릅니다.
단순히 그런 최적화에 신경을 쓰지 않아서일 수도 있고, 언어 자체의 특성상 안되는 경우도 있고, 그 언어가 돌아가는 플랫폼의 특성상 안되거나 어려운 경우도 있습니다.
self recursion만 최적화해주고, mutual recursion은 못해주는 경우도 있습니다.
TCO를 해달라는 keyword나 컴파일러 지시자가 있는 언어도 있습니다. 뭐 여하튼 다양합니다.
주로 쓰이는 C/C++ 컴파일러의 경우에는 상당히 잘 해주는 것으로 알고 있습니다.
gcc, msvc++, clang 모두 잘 해줍니다. 뭐 템플릿이 복잡하게 얽히거나하면 어쩔지 모르겠습니다만 일단 믿어도 될 겁니다.

neocoin의 이미지

+1

yielding의 이미지

+2

Life rushes on, we are distracted

oosap의 이미지

저도 +1 드립니다.

아래 제가 관련한 포스팅을 LLVM 메일리스트에서 주고 받아서 여기 링크를 올립니다.

http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-October/054054.html

http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-October/054056.html

http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-October/054082.html

마지막 글에는 77년도의 오래된 논문도 링크되어 있습니다.

아래 글은 반론이 있는 부분이라 이제는 삭제하고 싶지만,
무엇에 대한 반론이 있는지 남겨두기 위해 그냥 둡니다.

위 두번째 메일리스트의 내용입니다. John McCall 씨의 의견이구요. 저의 의미 전달에서 곡해가 있을지도 모릅니다.
 
제가 배운 또 한가지는 이 쓰레드에서 언급이 되었는지 모르겠는데 recursion 은 iteration 으로 바꿔줘야 한다고
가르치는 선생님/교수님이 있다면 잘못 가르치고 있을 가능성이 높다는 것입니다. 위 두 메일리스트 답변에
공통적으로 들어가 있는 이야기 인데 두번째 것에서 더 자세하게 논하고 있습니다. recursion 과 iteration 의 성능을
비교하는 데에는 스택과 호출/반환의 사용에 의한 비용과 같이 단순하게만 비교하기는 힘들고 실험결과 반대로
recursion 이 iteration 보다 성능이 우월하게 나타나기도 한다는 겁니다.

이 부분에 저는 의견이 없습니다. 저는 전문가가 아니거든요.. 단, 위 의견의 핵심은 recursion 이 iteration 보다 성능에서 뒤진다고 추상적으로 일반화하는 것은 옳지 않다는 주장입니다. 그 반례를 경험한 내용을 예를 들고 있습니다. 물론 그 분들의 의견일 뿐입니다.

Thanks for being one of those who care for people and mankind.
I'd like to be one of those as well.

winner의 이미지

사람은 평면구조(혹은 직선구조)를 재귀구조보다 쉽게 인식한다고 생각합니다. 재귀구조는 기본적으로 무한에 대한 개념을 내포하고 있고, 이것은 사람의 직관에 부합하지 않습니다. 제가 수학을 포기했던 이유가 무한대와 무한소였거든요.. ^_^.
성능에 대해서는 통상 iteration 이 recursion 보다 두배정도 좋게 나왔던 것으로 기억합니다. 물론 이것은 큰 의미는 없는 이야기이지만 recursion이 iteration 보다 성능이 좋게 나온다고 말한다면 그 실험에 대해서 의구심을 갖게 됩니다.
예를 들면 pointer 를 쓰는 것보다 값 복사를 쓰는 것이 성능이 좋게 나온다라는 이야기와 비슷하다고 생각합니다. 물론 가능성이 있고, 분명 유리한 상황이 있지만 많은 경우는 그렇지가 않죠. 값 복사는 성능 이외에 안정적인 code 를 작성하기 좋다는 장점도 있지만 TCO 가 가능한 직선구조에 대해서 recursion 을 장려하는 것은 동의하기 어렵습니다.

그런데 TCO에 있어 C++ 소멸자를 고려한 적은 없는데 확실히 그렇군요. 소멸자는 역으로 호출되어야 하니까... 좋은 것 배웠습니다.

oosap의 이미지

그럼 가능하다면 recursion 은 iteration 으로 바꾸는 것이 성능상 이득이라고 일반화 할 수 있다고 생각하시나요?

Thanks for being one of those who care for people and mankind.
I'd like to be one of those as well.

winner의 이미지

결국 compiler 에 의한 최적화가 이루어지는 것인가, 아니면 사람이 하는가의 차이죠.
그리고 결국 TCO 가 되는 재귀는 인지구조가 반복과 비슷합니다.
예를 들어 우리가 가장 흔히 접해서 편하다고 생각하는 재귀인 수학에서 가르치는 factorial 수식은 TCO 가 안 되죠.

oosap의 이미지

재귀.. 각각 장단점이 있어서 참 판단하기 어렵습니다.
의견도 분분한 것 같습니다. 저같은 경우는 재귀는 익숙치 않아서 쓰기도 쉽지 않지만 되도록이면 안쓰거나 조심해서 써야할 것 같다는 생각이 듧니다.

Thanks for being one of those who care for people and mankind.
I'd like to be one of those as well.

익명 사용자의 이미지

다른 예제를 얘기하려다가 착각하신거 같네요.
factorial은 꼬리재귀로 구현할 수 있습니다.

#include <stdio.h>
 
int factorial(int n, int result) {
  if(n == 1) return result;
  else factorial(n - 1, result * n);
}
 
int main(void) {
  printf("%d\n", factorial(10, 1));
  return 0;
}
winner의 이미지

n * factorial(n - 1)

익명 사용자의 이미지

위 내용중에 오해할만한 내용이 있는거 같습니다.

꼬리재귀(tail revursion, tail call)에 관련해서
이미 gcc는 *오래전부터* 지원해 주고 있었습니다.
아마 최적화 옵션을 -O2정도로 주면 꼬리재귀에 대해 최적화를 합니다.

그리고 함수형 언어라고 해서 재귀호출에 최적화 되어 있는 것도 아닙니다.
예를 들면 emacs에서 사용하는 elisp의 경우 꼬리재귀에 대한 최적화 기능이 없습니다.

제가 알기로 꼬리재귀가 재귀호출의 특수한 경우이기 때문에
recursion을 iteration으로 고쳐쓰기가 *기계적으로* 가능하고
그걸 자동으로 해주는 것이 꼬리재귀 최적화입니다.

꼬리재귀가 아닌 다른 재귀호출에 대해서는
최적화가 가능하지는 않은 걸로 알고 있습니다만...

우히히의 이미지

총 단계를 10단계로 두고 (숫자가 높을수록 고수)

1 ~ 3단계는 iteration 을 선호하고
4 ~ 6 단계는 recursion 을 선호하고
7 ~ 10 단계는 그래프 계열은 recursion 을 선호하고 그 외에는 iteration 을 선호하는 거 같네요.

사실 recursion 이 익숙해지면 정말 편리합니다.

중급 넘어가면 iteration 으로 나타내는게 더 힘든 문제들이 존재합니다.
그리고 iteration 으로 짜야지만 통과하는 문제들이 존재합니다. ㅠㅠ

1. 슬라이딩 윈도우
2. 점화식을 행렬화 한 뒤 거듭제곱 가속화

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.