C++ 상속 받은 클래스가 스택에 만들어지지 않도록 강제하는 방

bugiii의 이미지

안녕하세요?

상속 받을 클래스가 스택에 만들어지면 안되게 베이스 클래스가 강제할 수 있는 방법이 있을까요?

특정 클래스가 스택에 만들어지지 않도록은 소멸자를 protected 나 private 으로 하면 되는데요. 그것을 상속 받는 클래스까지 하게 할 수는 없었습니다.

혹시 다른 기법이 있을지 궁금합니다. 같이 고민 좀 부탁드립니다.

cedar의 이미지

답을 거의 알고 계신데 뭔가 착각하신듯...
MEC++ Item 27을 보세요.

bugiii의 이미지

cedar wrote:
답을 거의 알고 계신데 뭔가 착각하신듯...
MEC++ Item 27을 보세요.

그걸로 부족해서 드리는 질문입니다. 소멸자를 protected 로 한다고 해도 상속받은 클래스의 소멸자가 public 이라면 상속 받은 크래스를 스택에 만들 수 있다는 문제 때문입니다.

doldori의 이미지

저도 처음에 MEC++을 생각했는데 생각보다 까다롭군요. base class의 소멸자를
private로 하면 아예 상속을 할 수 없고, protected로 하면 그것만으로는 derived
class를 스택에 생성하는 것을 막을 수 없으니까요.
derived class의 소멸자도 protected로 만드는 방법밖에 생각이 나지 않는군요.
그런데 bugiii님도 이미 이 방법은 생각하셨겠죠.

bugiii의 이미지

doldori wrote:
저도 처음에 MEC++을 생각했는데 생각보다 까다롭군요. base class의 소멸자를
private로 하면 아예 상속을 할 수 없고, protected로 하면 그것만으로는 derived
class를 스택에 생성하는 것을 막을 수 없으니까요.
derived class의 소멸자도 protected로 만드는 방법밖에 생각이 나지 않는군요.
그런데 bugiii님도 이미 이 방법은 생각하셨겠죠.

네, 그렇게 하면 되는데 사용자들이 그렇게 안할 확률이 엄청 높다는게 문제라서요.

yielding의 이미지

융통성을 발휘해서

class no_stack_allocatable {
private:
    ~no_stack_allocatable();
};

class base: no_stack_allocatable {
public:
    base() {
       cout <<"base created\n";
    }
};

class derived: public base, no_stack_allocatable {
public:
    derived() {
        cout <<"delived created\n";
    }
};

이건 어떻습니다?

Life rushes on, we are distracted

htna의 이미지

굳이 스택에 만들어지지 않도록 강제하는 방법이 있지는 않은것으로 알고 있습니다.
그보다 스택에 강제할 수 있다는 얘기를 어디서도 들어보지는 못했군요...

강제할 수 있나요 ?

WOW Wow!!!
Computer Science is no more about computers than astronomy is about telescopes.
-- E. W. Dijkstra

bugiii의 이미지

htna wrote:
굳이 스택에 만들어지지 않도록 강제하는 방법이 있지는 않은것으로 알고 있습니다.
그보다 스택에 강제할 수 있다는 얘기를 어디서도 들어보지는 못했군요...

강제할 수 있나요 ?

굳이 필요하거든요. 사용자의 실수를 막고 싶은 (거의 대부분 실수를 할 것이 예상되는 부분) 라이브러리 제작자의 안타까움이랄까요...

스택에 강제하는 것은 delete 연산자를 숨기는 것으로 어느정도 시도해 볼 수 있지 않을까 합니다.

doldori의 이미지

yielding wrote:
융통성을 발휘해서
class no_stack_allocatable {
private:
    ~no_stack_allocatable();
};

class base: no_stack_allocatable {
public:
    base() {
       cout <<"base created\n";
    }
};

class derived: public base, no_stack_allocatable {
public:
    derived() {
        cout <<"delived created\n";
    }
};

이건 어떻습니다?


이 코드는 컴파일이 안되겠는데요.
yielding의 이미지

yielding wrote:
융통성을 발휘해서
class no_stack_allocatable {
private:
    ~no_stack_allocatable();
};

class base: no_stack_allocatable {
public:
    base() {
       cout <<"base created\n";
    }
};

class derived: public base, no_stack_allocatable {
public:
    derived() {
        cout <<"delived created\n";
    }
};

이건 어떻습니다?


대충생각해서 vc7.1에서만 컴파일해보고 올렸네요.. 엉터리 코드입니다. :oops:

그런데 동적할당은 안되고 스택에만 만들어지게는 할 수 있습니다.
아래처럼. exception handler에서 동적할당하는 것을 막게 하고싶을때 이렇게 하면 되겠죠.

class nonnewable {
protected:
   static void * operator new(size_t size);
   static void * operator new[](size_t size);
};
 
class base: public nonnewable {
public:
    base()    { std::cout << "base\n";    }
 };
 
 int main()
{
    base b0;
    base *b1 = new base(); // error
}

Life rushes on, we are distracted

bugiii의 이미지

스택에 만들어지는 것을 강제할 수는 없지만 소용없도록 하는 것을 다음과 같이 만들어 보았습니다. 목표는 라이브러리 사용자가 쓰레드를 생성하는 객체를 스택에 만드는 실수를 방지하는 것입니다.

라이브러리의 다른 파일들과 엮여있어서 바로 컴파일은 안되겠지만 의미만 한번 보시고 같이 고민 부탁드립니다.

ThreadStart 자체를 좀 더 개선하면 스택 크기를 담는 홀더 객체를 이용해서 디폴트 인자 비슷하게 흉내도 내보고 싶고, ThreadStart 의 리턴값이 생날짜의 쓰레드 핸들이 아니라 좀 더 의미있는 ThreadControl 같은 클래스의 생성자 인자 정도를 지원해서 detach 나 join 같은 것을 구현해보려고 합니다.

--- thread.h
#include <ptk/ptk.h>
#include <boost/noncopyable.hpp>


namespace ptk {

#if defined( _WIN32 )
	typedef unsigned long ThreadType;
	typedef unsigned ThreadReturnType;
#elif defined( PTHREAD )
	typedef pthread_t ThreadType;
	typedef void* ThreadReturnType;
#else
#	error
#endif

class ThreadExecutor : private boost::noncopyable
{
public:
	ThreadExecutor();

protected:
	virtual ~ThreadExecutor();

private:
	virtual ThreadReturnType operator()() = 0;

	friend ThreadReturnType IF_WIN32( __stdcall ) ThreadProc( void* arg );
};

ThreadType ThreadStart_( ThreadExecutor* e, unsigned stackSize );

template< typename T >
ThreadType ThreadStart( unsigned stackSize )
{
	return ThreadStart_(  new T, stackSize ); 
}

template< typename T, typename P1 >
ThreadType ThreadStart( P1 p1, unsigned stackSize )
{
	return ThreadStart_( new T( p1 ), stackSize ); 
}

template< typename T, typename P1, typename P2 >
ThreadType ThreadStart( P1 p1, P2 p2, unsigned stackSize )
{
	return ThreadStart_( new T( p1, p2 ), stackSize ); 
}

template< typename T, typename P1, typename P2, typename P3 >
ThreadType ThreadStart( P1 p1, P2 p2, P3 p3, unsigned stackSize )
{
	return ThreadStart_( new T( p1, p2, p3 ), stackSize ); 
}

} // namespace ptk

--- thread.cpp
#include <ptk/win32.h>
#if defined( _WIN32 )
#	include <process.h>
#endif
#include <ptk/pthread.h>
#include <ptk/thread.h>
#include <ptk/exception.h>
#include <ScopeGuard.h>

using namespace std;

namespace ptk {

ThreadExecutor::ThreadExecutor()
{
}

ThreadExecutor::~ThreadExecutor()
{
}

static ThreadReturnType IF_WIN32( __stdcall ) ThreadProc( void* arg )
{
	ThreadExecutor* e = reinterpret_cast<ThreadExecutor*>(arg);

	ThreadReturnType r = (*e)();

	delete e;

	return r;
}

ThreadType ThreadStart_( ThreadExecutor* e, unsigned stackSize )
{
#if defined( _WIN32 )

	unsigned threadId = 0;
	ThreadType thread_ = _beginthreadex( NULL, stackSize, ThreadProc, e, 0, &threadId );
	if( thread_ <= 0 )
		throw crtl_error( PTK_WHERE, "_beginthreadex" );

#elif defined( PTHREAD )

	pthread_attr_t attr;
	pthread_attr_init( &attr );
	ON_BLOCK_EXIT( pthread_attr_destroy, &attr );
	pthread_attr_setstacksize( &attr, stackSize );

	ThreadType thread_;
	if( pthread_create( &thread_, attr, threadProc, e ) != 0 )
		throw crtl_error( PTK_WHERE, "pthread_create" );

#else

#	error

#endif

	return thread_;
}

} // namespace ptk

--- test_thread.cpp
#include <ptk/win32.h>
#include <ptk/thread.h>
#include <iostream>

using namespace std;
using namespace ptk;

class TestThread : public ThreadExecutor
{
public:
	explicit TestThread( int count ) :
		count_( count )
	{
	}

private:
	ThreadReturnType operator()()
	{
		for( int i = 0; i < count_; ++i )
		{
			Sleep( 1000 );
			cout << "TestThread" << endl;
		}

		return 0;
	}

private:
	int count_;
};

bool testThread()
{
	TestThread a(1); // 의미없음

	// 쓰레드 실행자를 동적으로 만들고 실행하도록 한다.
	//
	ThreadStart<TestThread>( 5, 0 );

	// 이 함수가 종료되더라도 쓰레드를 만든 객체는 힙상에 존재

	return 0;
}
cinsk의 이미지

잠시 생각해 봤는데.. portable한 방법으로는 힘들 것 같습니다. 일단 전 C++ 전문가도 아니다보니.. :? stack에 class instance를 만들면 안되게 하는 방법보다, 무조건 heap에 만들게 하는 것은 좀 더 쉬울 것 같아서.. 다음과 같이 해 봤는데.. 음.. 조언 바랍니다:


#include <iostream>
#include <new>

class must_be_in_heap {
private:
  bool dyn_created;

public:
  must_be_in_heap() {}

  int do_work() {
    if (!dyn_created) {
      return -1;
    }
    return 0;
  }

  void *operator new(size_t size, const std::nothrow_t &nothrow) {
    void *p = ::operator new(size, nothrow);
    if (p) {
      must_be_in_heap *mbih = reinterpret_cast<must_be_in_heap *>(p);
      mbih->dyn_created = true;
    }
    return p;
  }
  void *operator new(size_t size) {
    void *p = ::operator new(size);
    if (p) {
      must_be_in_heap *mbih = reinterpret_cast<must_be_in_heap *>(p);
      mbih->dyn_created = true;
    }
    return p;
  }
  void operator delete(void *ptr, const std::nothrow_t &nothrow) {
    ::operator delete(ptr, nothrow);
  }
  void operator delete(void *ptr) {
    ::operator delete(ptr);
  }
};


int
main(void)
{
  must_be_in_heap in_stack;
  must_be_in_heap *in_heap = new must_be_in_heap;

  if (in_stack.do_work() < 0)
    std::cout << "in_stack.do_work() failed" << std::endl;

  if (in_heap->do_work() < 0)
    std::cout << "in_heap.do_work() failed" << std::endl;

  return 0;
}

일단, 일부러 dyn_created member를 초기화하지 않고, operator new()에서 강제로 이 것을 설정하는 방식인데... C++ 잘 아시는 분들이 조언바랍니다. 일단 Linux gcc에서 동작을 하긴 하는데.. :wink:

pinetr2e의 이미지

compile-time 이 아니라 runtime에 강제하는 방법이구요.
그런데 static 이 아닌 멤버 변수 dyn_created 가 false(0) 으로 초기화된다는 보장이 없고, reinterpret_cast 해서 생성자 호출전에 dyn_created 를 접근하는것은 말씀하신데로 portable 한 방법은 아닌것 같습니다.

cinsk wrote:
잠시 생각해 봤는데.. portable한 방법으로는 힘들 것 같습니다. 일단 전 C++ 전문가도 아니다보니.. :? stack에 class instance를 만들면 안되게 하는 방법보다, 무조건 heap에 만들게 하는 것은 좀 더 쉬울 것 같아서.. 다음과 같이 해 봤는데.. 음.. 조언 바랍니다:

#include <iostream>
#include <new>

class must_be_in_heap {
private:
  bool dyn_created;

public:
  must_be_in_heap() {}

  int do_work() {
    if (!dyn_created) {
      return -1;
    }
    return 0;
  }

  void *operator new(size_t size, const std::nothrow_t &nothrow) {
    void *p = ::operator new(size, nothrow);
    if (p) {
      must_be_in_heap *mbih = reinterpret_cast<must_be_in_heap *>(p);
      mbih->dyn_created = true;
    }
    return p;
  }
  void *operator new(size_t size) {
    void *p = ::operator new(size);
    if (p) {
      must_be_in_heap *mbih = reinterpret_cast<must_be_in_heap *>(p);
      mbih->dyn_created = true;
    }
    return p;
  }
  void operator delete(void *ptr, const std::nothrow_t &nothrow) {
    ::operator delete(ptr, nothrow);
  }
  void operator delete(void *ptr) {
    ::operator delete(ptr);
  }
};


int
main(void)
{
  must_be_in_heap in_stack;
  must_be_in_heap *in_heap = new must_be_in_heap;

  if (in_stack.do_work() < 0)
    std::cout << "in_stack.do_work() failed" << std::endl;

  if (in_heap->do_work() < 0)
    std::cout << "in_heap.do_work() failed" << std::endl;

  return 0;
}

일단, 일부러 dyn_created member를 초기화하지 않고, operator new()에서 강제로 이 것을 설정하는 방식인데... C++ 잘 아시는 분들이 조언바랍니다. 일단 Linux gcc에서 동작을 하긴 하는데.. :wink:

pinetr2e의 이미지

재밌는 주제고 뉴스그룹에서 당연히 다뤄 봤을거 같아서 찾아 봤습니다.

http://groups.google.co.kr/group/comp.lang.c++.moderated/browse_thread/thread/f19ff3f0b4db1b95/3c43f5d933ad658c?q=force+stack+derived&rnum=2&hl=ko#3c43f5d933ad658c

compile-time에 강제 하는 것은 힘들것 같고,
위 뉴스그룹 글의 마직막에 포스팅한 사람이 제안한 다음과 같은 방법이 표준에 부합한다면 한가지 방법이 될 수 있겠네요.
(cinsk 님이 제안한 방법의 좀더 정확한 표현이라고 볼수 있겠네요)

Quote:


 #include <cassert> 
 #include <cstddef> 
   struct rt_heapalloc_check 
   { 
     virtual ~rt_heapalloc_check() {} 
     void* operator new(std::size_t sz) { ++heap_check; return ::operator new(sz); } 
     void operator delete(void* p) { ::operator delete(p); } 
   protected: 
     rt_heapalloc_check() { assert(--heap_check==0); } 
   private: 
     static int heap_check; 
   }; 
   int rt_heapalloc_check::heap_check = 0; 


   struct foo : rt_heapalloc_check {}; 


   int main() 
   { 
     //B b; //at runtime this will fail an assertion 
     B* b= new B; //works fine 
     return 0; 
   } 

cinsk의 이미지

:oops: 이런 초보적인 실수를 하다니...pinetr2e님이 말씀하신 것처럼 초기화되지 않았으니 문제가 심각하군요. 뉴스 그룹에서 보셨다는 방법처럼 증가/감소하는 방식을 쓰는 게 더 나을 듯 싶습니다.

doldori의 이미지

pinetr2e님이 소개하신 방법이 괜찮아 보이는군요. 이식성에도 문제가 없을 것 같습니다.
저는 다른 얘기를 하고 싶은데 ThreadProc() 함수에 예외 안전을 고려하시는 것이
어떨까 합니다.

ThreadReturnType IF_WIN32( __stdcall ) ThreadProc( void* arg ) 
{ 
    auto_ptr<ThreadExecutor> e(static_cast<ThreadExecutor*>(arg)); 

    ThreadReturnType r = (*e)(); 

    return r; 
} 

그리고 템플릿으로 만든 몇 개의 함수의 의미가 재미있군요. factory pattern 비슷한
효과를 내는 것 같네요.

htna의 이미지

굳이 new operator를 재작성(overloading인지 overriding인지.. 철자가 틀리려나.. ㅡ.ㅜ) 해서 heap을 체크할 필요성이 있나 모르겠군요...
그 나름대로의 장점이 있기는 하지만...
컴파일타임에 에러를 확인 못하는거.. 너무 치명적이지 않을까 합니다.
stack에 만들지지 않는 방법을 찾는다면...
그냥 이렇게 하는게 편할듯 하네요...

class CHeapOnly
{
    CHeapOnly() { }
public:
    ~CHeapOnly() { }
    static CHeapOnly* CreateObject() {
        return new CHeapOnly();
    }
};

CHeapOnly stackobject; // compile time error
CHeapOnly* heapobject = CHeapOnly::CreateObject(); // no error

WOW Wow!!!
Computer Science is no more about computers than astronomy is about telescopes.
-- E. W. Dijkstra

doldori의 이미지

htna wrote:
굳이 heap을 체크할 필요성이 있나 모르겠군요...
stack에 만들지 는 방법을 찾는다면...
그냥 이렇게 하는게 편할듯 하네요...
class CHeapOnly
{
    CHeapOnly() { }
public:
    ~CHeapOnly() { }
    CHeapOnly* CreateObject() {
        return new CHeapOnly();
    }
}

CHeapOnly stackobject; // compile time error
CHeapOnly* heapobject = CHeapOnly::CreateObject(); // no error

그렇게 간단히 해결되지 않습니다. 이것은 소멸자를 private으로 만든 것과 본질적으로
같습니다. 문제는 이 클래스를 상속받은 클래스에서도 힙에만 생성되도록 강제하는
것이죠. 그런데 이 구조는 상속 자체가 안됩니다. 만약 CHeapOnly의 생성자를
protected로 만든다고 해도
class Derived : public CHeapOnly { };

Derived d;  // this should be an error, but it's not
htna의 이미지

doldori wrote:
htna wrote:
굳이 heap을 체크할 필요성이 있나 모르겠군요...
stack에 만들지 는 방법을 찾는다면...
그냥 이렇게 하는게 편할듯 하네요...
class CHeapOnly
{
    CHeapOnly() { }
public:
    ~CHeapOnly() { }
    CHeapOnly* CreateObject() {
        return new CHeapOnly();
    }
}

CHeapOnly stackobject; // compile time error
CHeapOnly* heapobject = CHeapOnly::CreateObject(); // no error

그렇게 간단히 해결되지 않습니다. 문제는 이 클래스를 상속받은 클래스에서도
힙에만 생성되도록 강제하는 것이죠. 그런데 이 구조는 상속 자체가 안됩니다.
만약 CHeapOnly의 생성자를 protected로 만든다고 해도
class Derived : public CHeapOnly { };

Derived d;  // this should be an error, but it's not

상속이라는 제한이 있었군요..
ㅎㅎ
너무 직접적인 요구를 잊었네요...
orz

WOW Wow!!!
Computer Science is no more about computers than astronomy is about telescopes.
-- E. W. Dijkstra

bugiii의 이미지

doldori wrote:
pinetr2e님이 소개하신 방법이 괜찮아 보이는군요. 이식성에도 문제가 없을 것 같습니다.
저는 다른 얘기를 하고 싶은데 ThreadProc() 함수에 예외 안전을 고려하시는 것이
어떨까 합니다.
ThreadReturnType IF_WIN32( __stdcall ) ThreadProc( void* arg ) 
{ 
    auto_ptr<ThreadExecutor> e(static_cast<ThreadExecutor*>(arg)); 

    ThreadReturnType r = (*e)(); 

    return r; 
} 

그렇죠!!! delete 해야 한다는데 급급한 나머지 그걸 빠트렸군요. 좋은 지적 감사합니다. 그리고 static_cast 가 되는군요. 고맙습니다.

doldori wrote:
그리고 템플릿으로 만든 몇 개의 함수의 의미가 재미있군요. factory pattern 비슷한 효과를 내는 것 같네요.

네, 목표를 정하고 다시 생각해보니 자연스럽게 그런 형태가 나오더군요. 원래의 주제처럼 동적 할당을 강제하는 것이 아니라 제시한 형태로 사용하지 않으면 동작하지 않도록 하면 된다는 것에 초점을 맞춰보았습니다.

제가 원하는 목표를 제시하지 않고 너무 일반적인 주제로 질문을 올려서 질문 자체가 잘못된 것이 아닌가 걱정이 되긴 했지만, 다른 여러분의 좋은 의견도 있고 새로운 아이디어도 얻을 수 있어서 좋았습니다.

pinetr2e 님이 제시하신 방법도 좋아보입니다. new / delete 의 다른 형태도 지원한다면 좀 더 완벽해질 것 같습니다.

하여튼, 사용자에게 어떤 형태로든 사용법을 강제해서 잘못 사용하는 것을 방지하고자 하는 것은 어느정도 만족했다고 생각합니다. 이제 조금 더 다듬어서 마무리 해야겠습니다.

역시나 라이브러리 제작이라는 것이 그 구현 자체도 고민이 되는 것이지만 사용자가 실수할 부분을 미연에 방지하는 것 또한 시간이 많이 걸리고 노력이 필요한가 봅니다.

그럼, 좋은 휴일 되시구요. 다시 한번 좋은 의견 감사드립니다.

댓글 달기

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