임베디드에서 -o2 옵션으로 최적화 문제

삼구의신의 이미지

임베디드 시스템에서 g++ 컴파일을 하는데 -O2 옵션으로 최적하 하는것이 문제가 될까요?

어떤분은 컴파일러가 최적화하면서 레지스터도 건드리고 하기 때문에 치명적인 위험이 있다고 하는데..

정말 그런가요?

라스코니의 이미지

아래와 같은 문제점이 있습니다.
1) 최적화 옵션을 넣으면 코드 debug가 어렵다.
2) 디버그 모드일때는 디버그 용 라이브러리끼리 모으고, 릴리즈 모드(-O2??)일때는 릴리즈 용 라이브러리끼리 모아야 하는데 어렵다.
3) 간혹 릴리즈 모드일때 생각(설계) 외로 동작하는 코드가 있다. 그때 사용하는 릴리즈 용 라이브러리와 충돌이 있을수도 있고 최적화 관련하여 멋대로 컴파일러가 뜯어 고쳐서 발생하는 문제도 있다.

-O2 옵션 주고 컴파일하면 성능이 비약적으로 좋아지죠. 하지만 디버그 모드에서 멀쩡히 돌아가던 코드도 릴리즈 모드에서는 안돌아가는 경우도 있으니 릴리즈 코드 테스트에 신경을 많이 쓰셔야 합니다. 디버그 모드에서 테스트 충분히 했다고 해서 릴리즈 모드로 컴파일 한 후 형식적으로 테스트하고 배포하면 안된다는 뜻입니다.

삼구의신의 이미지

라스코니 말씀은 주의해서 써야한다는 건가요?
혹시 실무에서 최적화 옵션을 사용하시나요?

라스코니의 이미지

거의 사용하지 않습니다. 다만 아주 빠른 성능이 필요한 경우라면 사용을 고려해 봐야 겠지요.
디버그 모드에서도 그럭 저럭 돌아가면 굳이 릴리즈 모드로 바꾸어 또 고생을 할 필요는 없죠.

하지만 만약 로직 자체는 문제가 없는데 성능이 좀 부족하다면 릴리즈 모드를 고민하셔야겠죠.

삼구의신의 이미지

디버그 모드라고 하시는것이 컴파일 옵션에서 -g 넣는것을 말씀하시는거죠?
-g 옵션을 넣는게 파일 크기만 커지는것이 아니라 성능자체도 느려지게 만든다는것인가요?

라스코니의 이미지

-g -O0 로 컴파일 옵션을 주는 것을 디버그 모드라고 볼 수 있고, 출시 때 -O0 로만 컴파일할 수도 있고 필요시 -O1, O2 등을 줄수도 있겠죠.

성능자체가 느려지는 것이 아니라 그냥 기본 상태라고 보시면 됩니다. 최적화 컴파일 전의 기본 상태죠.

jick의 이미지

프로그램을 *제대로* 짰으면 최적화를 어떻게 하든 잘 돌아야 합니다.

물론 C(++)을 아무 문제 없이 *제대로* 짜는 건 거의 "너희 중 죄없는 자가 돌을 던져라" 수준의 요구사항이라서 실제로 잘 돌지는 그때그때 다릅니다만...

...저라면 일단 최적화 옵션을 켜서 돌려보는 쪽을 추천하겠습니다. "원래 잘 도는 코드인데 최적화를 키면 문제가 생기는" 코드는 원래부터 문제가 있었는데 그냥 우연히 운이 좋아서 컴파일러가 문제가 안생기는 방향으로 컴파일을 했다든지, 속도가 너무 느려서 문제가 발생할 가능성이 낮아서 괜찮아 보인다든지, 그럴 가능성이 매우 큽니다. 이 기회에 한번 상황을 파악해 보는 것도 좋겠죠. (뭐 고치는 데 시간이 얼마나 걸릴지는 그때그때 다릅니다만..)

삼구의신의 이미지

jick님 말씀대로 제대로 안짜서 오류가 난다는게 맞는거 같네요.

제대로 짰을때 최적화를 한다고 해서 오동작이 일어날 가능성은 없다는거죠?

라스코니의 이미지

사실 완벽히 legit 한 코드임에도 불구하고 -O0 상태에서는 잘 되던 것이 -O2에서는 안되는 것도 경험하실 수 있습니다. 그럴때 volatile 키워드를 쓰기도 하고 #ifdef DEBUG ~~ #endif 등으로 코드에 조건부 컴파일 directives 로 구분을 지을 때도 있고요.

까다로운 점은 이런 경우 오류가 발생한 위치를 찾기도 어렵고 해결 방법을 찾기도 어렵다는 것이 것입니다.

그런데의 이미지

저는 잘 이해하기 어렵네요. Gentoo를 쓰고 있고, 별다른 설정을 해주지 않으면 많은 코드가 -O2로 최적화되어 배포됩니다. firefox나 thunderbird 같은 것들도 보통 -O2로 컴파일 한다고 생각합니다. gcc 자체도 그렇고요. legit한 코드임에도 불구하고 문제가 생기는 경우가 많아서 상대적으로 컴파일러 최적화를 꺼야 하는 게 일반적으로 정도로 보이지는 않습니다. 그보다는 현재 일하고 있는 곳에서 주어진 코드의 수준 문제일 것 같습니다. 아마도 라스코니님이 보고/짜고 있는 코드는 legit하지만 기존 코드가 그렇지 못 하다거나, 이런 상황이 아닐까 싶네요.

라스코니의 이미지

임베디드 코딩 환경에서는 심심찮게 -O2 최적화에 의한 문제점을 볼수 있습니다. 왜냐하면 하드웨어는 특정 순서대로 세팅해줘야 제대로 동작하는데 최적화는 그 순서를 제멋대로 바꿔버리는 수도 있거든요.
일반 데스크 탑 운용 환경에서는 거의 문제없으리라 생각됩니다.

익명 사용자의 이미지

엄밀히 말하자면 C언어 표준에는 statement들이 실제로 프로그램에 쓰인 순서대로 실행되어야 한다는 규칙은 없습니다.
그래서 저런 코드는 사람마다 legit한 코드냐 아니냐 이견이 있을 수 있습니다.

표준과 상식사이의 틈 때문에 최적화에 관련된 문제는 다루기 까다로운 경우가 많지요.

라스코니의 이미지

너무 당연하기 때문에 그런 규칙이 없는거 아닐까요? 캐쉬 최적화나 레지스터 재사용을 고려하면 당연히 컴파일러 입장에서 가능한 많은 부분에 대해서 최적화를 하고 싶은 유혹을 뿌리치기 힘들 겁니다.

gcc 의 최적화 기능이 매우 훌륭하다고는 하나 컴파일러 역시 사람이 만든 창작물이고 모든 경우를 커버하지는 못합니다. 자주는 아니지만 프로그래밍 QnA 사이트에 디버그 모드에서는 잘 도는데 릴리즈 모드로 돌리니까 안되요 이런 글이 심심찮게 올라옵니다. 문제는 이 경우 원인을 찾기 어렵다는 것인데 하지만 원인만 발견하면 고치는 건 의외로 쉬울때도 많죠. 이렇게 작성된 코드들 모두 legit 코드이지만 디버그/릴리즈 모드에서 각각 다르게 동작하는 것을 보면 컴파일러 이슈라고 볼 수 밖에 없습니다.
그래서 컴파일러도 volatile, memory barrier 등의 키워드를 허용하여 최적화를 막을 수 있게 끔 하고 있습니다. 이것을 보면 컴파일러 스스로 자신의 최적화에 의해서 사용자(프로그래머)가 의도한 바대로 동작하지 않을 수 있음을 인정하는 것이라고 볼 수 있겟죠.

익명 사용자의 이미지

제가 말씀드리고 싶은 것은 컴파일러의 저러한 최적화가 컴파일러의 오류가 아니라, 표준의 범위 내에 있는 행동이라는 것입니다. 라스코니님이 예로 드신 것도, 컴파일러는 어디까지나 표준의 범위내에서는 아무 문제 없도록 최적화를 한 것 입니다. 하지만 외부 하드웨어 부분은 표준에서 보장되는 범위 밖인데, 그것을 프로그래머가 오해하였기에 문제가 발생한 것입니다.

코드는 완벽한데 최적화 때문에 버그가 생겼다는 생각보다는, 우리가 가진 오해나 잘못된 가정이 무엇인지 알아가는게 좀 더 발전적이 아닐까요.

"유혹"이라고 하시니, 최적화를 마치 컴파일러가 못할짓을 한 것 처럼 들리네요.

라스코니의 이미지

최적화를 통해서 성능이 높아질수록 컴파일러의 명성(?)이 높아지니 컴파일러 설계자의 입장에선 가능한 많은 부분에서 최적화를 싶어하고 싶을것 같고요.

하드웨어 부분은 표준에서 보장하는 범위밖이라는 인식이 저와는 다른 것 같습니다. 그리고 프로그래머가 코딩한 순서대로 컴파일러가 수행할 의무가 없다고 생각하시는 점도 저와 다르네요.

저는 결과가 정확하다면 컴파일러가 순서를 바꾸든 다른 어떤 방법을 써서 최적화를 하던 상관없다고 생각합니다. 확실히 하드웨어를 다루는 부분은 전체 시스템 코드 분량에서 보면 마이너하니깐요.

말씀하시는 표준의 범위가 어떤 것인지 궁금합니다. 하드웨어 부분은 표준에서 보장하는 범위 밖이라는 것은 저한테는 생소하네요.

저는 최적화가 컴파일러 오류라고 지적한 적이 없습니다. 다만 컴파일러 최적화가 모든 유저에게 만족을 주는 것은 불가능하기 때문에 대를 위해 소를 희생할 수 밖에 없고, 그에 따라 하드웨어의 경우 유저가 생각한데로 동작하지 않도록 최적화가 될 수 있다고 언급한 적은 있습니다. 그러면 유저는 당연히 volatile, mem barrier 등을 써서 해당 부분이 최적화가 안되도록 하면 되는 거구요. 모두 legit 한 코드입니다. 모두 gcc 메뉴얼에 있는 대로 사용하는 것이며 어떤 불법적이고 gcc 기능을 우회하려고 하는 시도도 않는 좋은 코드입니다. 그렇지 않으면 모든 firmware, embedded 개발자는 해커이게요?

이 코딩/개발 과정에는 잘못된 가정이나 오해가 개입하지 않습니다. 도리어 하드웨어의 특성을 잘 이해하고 프로세서 동작을 잘 이해하는 능력이 요구되지요.

라스코니의 이미지

제가 사용하는 OS에서 얼마나 volatile이 사용되는지 체크해 보니 5000번 가까이 volatile 변수 또는 volatile 메모리 접근으로 사용되고 있네요.

모두가 친숙한 linux의 경우 커널 4.9 소스를 받아서 volatile이 얼마나 사용되었는지 보니 1,695 개 파일에서 volatile이 사용되고 있습니다.

익명 사용자의 이미지

1. 고급 언어에 있어서 컴파일러의 최적화 기능은 컴파일러의 명성 수준의 문제가 아닙니다. 최적화를 끈 채로 컴파일해서 생성된 결과물을 어셈블리로 읽어보면 정말 말도 안 된다 싶을 정도로 비효율적입니다. 요즘처럼 데스크탑 컴퓨터 성능이 좋은 시대에 아주 잠깐 돌아가고 말 프로그램이라면 상관 없겠으나, 프로그램이 좀 computational-intensive하다 싶으면 몇 초 대 몇 분 정도로 차이가 나는 경우도 쉽게 봅니다.

2. “결과가 정확하다면 컴파일러가 순서를 바꾸든 다른 어떤 방법을 써서 최적화를 하던 상관없다”: 맞는 말씀이십니다. 그게 표준이 보장하는 내용이기도 하고, 컴파일러 최적화는 그 보장 안에서 최적화를 할 자유가 있는 것이죠. 다만 “결과”가 어디까지인지가 문제인데, 거칠게 설명하자면 사실 CPU 내부에서 일어나는 시시콜콜한 각종 연산이나 메모리 접근 같은 건 밖에서 보이지 않으니 C/C++언어 표준이 보기엔 “결과”에 속하지 않는다는 겁니다.

임베디드나 OS 등 컴퓨터에 대한 좀 더 세밀한 제어가 필요한 경우에는 언어 표준이 보장하는 메커니즘(volatile 등) 안에서 어떻게 해보거나, 안 되면 컴파일러 확장 기능을 쓰든 필요한 부분만 어셈블리로 따로 짜서 링크하든 해서 해결해야겠지요. 뭐 그건 좋습니다. 그건 그렇다 치고..

그렇다 해도 디버그 모드(혹은 최적화를 끈 모드)와 릴리즈 모드(최적화가 적당히 켜진 모드) 빌드 사이에, 동작 성능에 관련된 것 외에 눈에 띄는 차이가 발생하는 현상은 납득하기 어렵지요. 프로그램의 동작을 깨트릴 수도 있는 정말 위험한 일부 최적화 기법(이런 것들은 대개 따로 수동으로 켜지 않으면 보통 디폴트로 꺼져있습니다)들을 제외하면, 이건 컴파일러 경고 메시지만큼이나 소스 코드 품질에 의문을 갖게 만드는 징조입니다. 뭔가 소스 코드에 프로그래머의 의도가 불완전하게 담겨서 컴파일러가 제대로 전달받지 못했는데, 우연히 디버그 모드에서는 잘 작동하는 것 아닐까 하는 인상을 주는 것이죠. 프로그래머의 주의 깊은 분석 결과 정말로 어쩔 수 없었던 경우를 제외하고는, 그런 경우는 웬만하면 없는 게 좋습니다.

라스코니의 이미지

의견의 요지는 알겠습니다. 어찌되었든 디버그 모드와 릴리즈 모드에 성능외의 차이가 있다면 소스 코드 품질에 문제가 있다는 것이지요? "그리고 그건 그렇다 치고... 그렇다 해도" 라는 말에 충분히 이해했습니다. 리누스 토발즈에게 따끔하게 이야기하겠습니다(농담입니다).

구글에 "release mode bug" 로 검색해 보시면 많은 관련 결과가 나옵니다. 한번 보시길 바랍니다. 작성자의 코드 품질을 의심해 봐야 한다는 답변이 있다면 알려 주시기 바랍니다. 밑에는 미신까지 언급하셨는데 컴파일러를 하나의 종교처럼 신봉하시는 것 같군요.

익명 사용자의 이미지

1. 아니요. “소스 코드 품질에 문제가 있을 가능성이 높다” 입니다. 컴파일러 경고 메시지처럼요. 제가 의식적으로 강한 표현을 사용하는 걸 피했다는 걸 유의하시길 바랍니다. 대부분의 실무 엔지니어링 관련 문제들처럼 여기서도 가끔은 유연하게 넘어가야 할 때가 있는 법이지요.

2. 리누스 토발즈 운운하며 권위에 의존하시려는 모습은 뭐 농담이신 줄로 알고 넘기겠습니다.

3. 네, 검색해 봤습니다. 별로 어려운 일도 아닌데요 뭘. 검색 결과 맨 위에 나오는 건 이거더군요:

https://stackoverflow.com/questions/1762088/common-reasons-for-bugs-in-release-version-not-present-in-debug-mode

다양한 답변들이 있으니 (혹시 안 해 보셨다면) 직접 살펴보시면 좋을 것 같습니다. 누구의 잘못인지 모호한 경우들도 더러 있습니다만, 작성자의 코드 품질에 원인이 있는 경우도 분명히 있지요. 사실 이런 문제 관련해서 가장 흔히 저질러지는 잘못이 ASSERT 매크로 표현식 안에 기능을 구현해 넣는 경우랍니다. 가끔 다른 언어로 프로그래밍하다 오신 분들은 C/C++에서 ASSERT가 문자 그대로 흔적도 남기지 않고 사라질 수도 있다는 걸 모르시는 경우가 있더라고요.

4. 네. 컴파일러는 완벽무결하지 않습니다. 물론 아무리 그래도 언어 표준이나 해당 컴파일러 문서 등에서 보장하고 있는 사항이 제대로 지켜지지 않는 버그가 있다면 “릴리즈 모드”라는 이름이 부끄러울 지경이죠. 그런 치명적인 버그는 최대한 빨리 고쳐져야 마땅합니다만, 어쨌든 컴파일러도 사람이 만든 건데 왜 버그가 없겠어요.

그리고 코드가 알 수 없는 이유로 디버그 모드에서만 돌고 릴리즈 모드에서는 안 도는 상황에서 당장 어떻게든 돌아가는 프로그램을 만들어야 하는 경우에는 부득이 workaround를 해야만 하는 경우가 있을 수도 있지요. 문제가 생긴 코드만 디버그 모드로 컴파일해서 링크한다던가, 근본적인 해결책이라고 할 수는 없더라도 어쩌겠습니까.

다만, “사실 완벽히 legit 한 코드임에도 불구하고 -O0 상태에서는 잘 되던 것이 -O2에서는 안되는” 경우, 어쨌건 어딘가 문제가 있을 가능성이 높다는 거지요. 그게 컴파일러 책임이거나, 사실은 legit하지 않았는데 제가 잘못 알았던 것이거나. 저는 제 프로그램에 문제가 생길 경우 저 자신부터 먼저 의심하라고 배웠습니다. 무죄가 밝혀지기 전까지는. 물론 OS, 플랫폼, 프레임워크, 컴파일러 등이 완벽무결하지는 않겠지만, 그럼 누굴 먼저 의심하겠어요?

...의 이미지

리눅스를 컴파일 할 때도 O2를 씁니다. 저는 데탑과 서버 쪽 리눅스 배포판에서 일하는데 거기도 커널팀이 가장 최신 컴파일러를 쓰고, 당연히 최적화 옵션들을 주의깊게 켜서 사용합니다. 말씀하신 내용들은 언어 표준의 밖의 일이기 때문에 컴파일러는 상관할 필요가 없고 프로그래머가 주의를 기울여야 하는 경우들 아닌가 싶습니다.

예컨대 변수 x를 레지스터 초기화 해주기 위해 값을 써주고 읽기만 한다면 constant로 컴파일러가 대체해 버릴 수도 있는데 임베디드 머신에선 문제가 되겠죠. 하지만 constant로 대체하는 건 표준의 약속에서 허용되는 부분이므로 x가 날아가는 게 싫다면 프로그래머가 volatile을 줘야 합니다. 이런 노력이 최적화로 인한 성능 및 전력 소비 향상의 대가라면 충분히 할 만하다고 일반적으로 받아들여진다고 생각합니다.

익명 사용자의 이미지

Quote:
너무 당연하기 때문에 그런 규칙이 없는거 아닐까요?

어... 너무 당연하다는 듯이 그렇게 말씀하시니까 말문이 막히네요..
C/C++ 표준에서 “당연해 보이는 것”들을 “당연하지 않게” 설명하려고 얼마나 공을 들여 놨는지 혹시 보셨나요? 읽기가 벅찰 지경입니다.
사실 읽다 보면 소스 코드가 아니더라도 이쯤 복잡하다 보면 어딘가 버그가 있지 않을까 싶을 정도에요.

하지만 이건 일단 철학의 문제입니다. C++을 비롯한 많은 프로그래밍 언어들은 소위 “As-if rule”이라는 것을 따르지요. 구체적인 내용까진 모르더라도 이런 게 있다는 건 알아 두시면 좋습니다.

https://en.cppreference.com/w/cpp/language/as_if

Quote:
이렇게 작성된 코드들 모두 legit 코드이지만 디버그/릴리즈 모드에서 각각 다르게 동작하는 것을 보면 컴파일러 이슈라고 볼 수 밖에 없습니다.

?! legit한 코드인 줄은 어떻게 알아요? 컴파일러에 문제가 절대 있을 수 없는 것은 아니지만, 프로그래밍 QnA 사이트에 올라온 소스 코드와 비교되고 있다는 점을 생각해 보면 불공평할 정도로 컴파일러만 의심을 받고 있는 것 같은데요?

익명 사용자의 이미지

volatile 키워드를 써야 할 곳에 안 썼다면 잘못된 프로그램인 것이지요. 그리고 최적화 관련 버그 리포트가 되어 있지 않은 gcc의 -O2 옵션으로 정상 동작하지 않는다면 버그가 잠재하고 있다고 보는 게 타당합니다. 보통 포인터 오류가 나 있거나 메모리를 어딘가 덮어쓰고 있지요. 이 글 덕분에 프로그래밍 미신이 또 하나 늘어났군요.

익명 사용자의 이미지

대부분의 경우는 제대로 안짜서 나는게 맞긴 합니다.
하지만 jick 님 말씀대로 "제대로 짠다"는게 굉장히 힘든일인데, 수백페이지에 달하는 언어표준에 무엇이 정의되어 있고 무엇이 아닌지 명확하게 알아야 하기 때문입니다.

https://pubweb.eng.utah.edu/~cs5785/slides-f10/Dangerous+Optimizations.pdf
여기에 최적화로 생길 수 있는 문제 사례 몇가지가 소개되어 있으니 한번 참고해 보세요.

익명 사용자의 이미지

10년 넘게 일하면서 -O2 안쓴적은 없는데 알수 없는 이유로 시간&노력을 따져봐서 해당 파일만 최적화 안하고 빌드해서 합치기도 하고 그러기도 합니다. 10년간 딱 한번 있었네요...

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.