sprintf 와 std::string의 결합

aswip의 이미지

얼마전부터 c언어의 sprintf 와 같은 함수를 c++ stl 적용하기 위해서 이런 정보를 찾고 있었습니다. 물론 예전 부터, ostringstream 같은 것을 알고 있었지만, 제 목적은 ansi-c의 sprintf와 string과의 결합이었습니다.
이것저것 찾아보니, asprintf 와 같은 함수가 가변길이 문자열의 예상 길이를 계산하여주는 편리함을 제공하기는 하지만, win32에는 그런함수가 없어서, asprintf는 포기 ㅠㅠ;;

결국 적절한 해법을 찾지 못하고, 다음과 같이 구현하고 말았습니다.

움... 찾아보면, 무언가 더 쉬운 방법이 있을것 같은데, 설마, 저와 같은 고민을 해본 c++ 개발자가 한명이라도 있었겠죠? ^^;;;

#include <iostream>
#include <stdarg.h>
#include <sstream>

using namespace std;

unsigned int GetDigit ( const char* ptrFormat, int &nIdx, int nFmtLen )
{
	if ( ( ptrFormat == NULL ) || ( nIdx >= nFmtLen ) )
		return 0;

	ostringstream oDigit;

	while ( isdigit ( ptrFormat[nIdx] ) > 0 )
	{
		oDigit << ptrFormat[nIdx];
		nIdx++;

		if ( nIdx >= nFmtLen )
			break;
	}

	return atoi ( oDigit.str().c_str() );	
}


string::size_type strprintf ( string& rstr, const char *pFmt, ... )
{
	bool bFMT = false;
	char* psz = NULL;	
	int nExpChars = 0, nIdx = 0, nFmtLen = strlen ( pFmt );
	
	if ( nFmtLen <= 0 )
		return 0;
		
	va_list arg;
	va_start (arg, pFmt);

	// 1. vsprintf 를 사용하기 위해서 포멧 문자열 길이 계산	
	while( pFmt[nIdx] )
	{		
		switch ( pFmt[nIdx] )
		{
		case 's':	//string 
			if ( bFMT )
			{
				nExpChars += strlen ( (char*)va_arg(arg, char*) );
				bFMT = false;
			}
			break;
		case 'f':	// Decimal floating point
		case 'd':	// Signed decimal integer
		case 'i':	// Signed decimal integer
		case 'g':	// Use shorter %e or %f
		case 'G':	// Use shorter %E or %F
		case 'o':	// Signed octal
		case 'x':	// Unsigned hexadecimal integer
		case 'X':	// Unsigned hexadecimal integer (capital letters)
		case 'p':	// Prints the address of the argument in hexadecimal digits.
			nExpChars += 16;
			bFMT = false;
			break;
		case 'c': 	// char 
			nExpChars++;
			bFMT = false;
			break;

		case '.':
			nIdx++;			
			nExpChars += GetDigit ( pFmt, nIdx, nFmtLen );
			nIdx--;
			break;		
		case '%':						
			if ( nIdx > 0 )
			{
				bFMT = (  pFmt[nIdx-1] == '%' ) ? false : true;				
			}
			else
			{
				bFMT = true;
			}

			nIdx++;
			nExpChars += GetDigit ( pFmt, nIdx, nFmtLen );
			nIdx--;
			break;
		default:
			nExpChars++;			
		}

		nIdx++;
		if ( nIdx < 0 || nIdx >= nFmtLen )
		{
			break;
		}
	}

	va_end (arg);
	
	// 2. 버퍼 크기를 계산된 포멧 문자열의 길이만큼 할당..
	rstr.resize(nExpChars, ' ' );
		
	va_start ( arg, pFmt );	
	
	// 3. vsprintf 사용
	int nPrnChars = vsprintf ( (char*)rstr.c_str(), pFmt, arg );

	if ( nPrnChars >= 0 && nPrnChars <= nExpChars )
	{
		rstr.erase ( nPrnChars );
	}
	
	va_end(arg);
	return rstr.length();	
}

int main ( int argc, char** argv )
{	
	string strRet;
	strprintf ( strRet, "floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);

	// 결과비교.
	printf ("floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);
	printf("%s\n", strRet.c_str());
	return 0;
}
케인의 이미지

저도 printf의 출력을 ostream으로 하고 싶어서 구현해 본 적이 있습니다. ^^;

http://hiya.byus.net/prog/archives/000063.html

기존에 제공되는 함수를 조합해서 만들고도 싶었습니다만, 중간에 버퍼를 이용해야 하는 제약 등이 있어서 포기하고, 직접 printf를 구현할 수 밖에 없었습니다.
가장 큰 장점은 사용중 어떠한 메모리 할당도 필요하지 않다는 것입니다.

vsnprintf와 같은 함수는 출력의 예상길이를 알기 위해서 사용할 수 있습니다. 길이를 알기 위해서 파싱할 필요까지는 없답니다. ^^;
(유닉스 계열에서는 snprintf를 사용하면 버퍼가 모자랄 경우 필요한 길이를 리턴합니다만, msvc는 그냥 0(에러값)을 리턴해버리지요. 하지만, %n 스펙을 사용해서 예상 길이는 알 수 있었던 것 같습니다. ^^;;)

boost 라이브러리에서는 좀 더 type safe한 방식으로 포멧팅 기능을 제공한다고 합니다만, printf와 같은 (위험하지만) 익숙한 인터페이스가 필요하기도 하지요. ^^;

cinsk의 이미지

GNU extension인 vasprintf() 또는 asprintf()를 타 non-GNU 시스템에서 쓰고 싶으시면 gcc나 gdb의 소스에서 libiberty 라이브러리를 가져다 쓰시면 됩니다.

happyjun의 이미지

boost::format 을 사용하시면 됩니다.

using namespace std;
using namespace boost::format;

format fmt( "printf like syntax : %d %d\n" );
fmt % 10 % 20;

cout << fmt;                        // printf like syntax : 10 20
string result = fmt.str();      // printf like syntax : 10 20

// or

string result2 = str ( format( "printf like syntax : %d %d\n" ) % 10 % 20 ) );    // same result

위의 것은 printf 스타일의 용법이고 .net 과 같은 스타일이 기본 용법입니다. type을 명시하지 않고 위치만을 명시합니다.

using namespace std;
using namespace boost::format;

cout << format( "%1% %2%" ) % "test" % 20 << endl;            // test 20
cout << format( "%2% %1%" ) % "test" % 20 << endl;            // 20 test
cout << format( "%1% %2% %1%") % "test" % 20 << endl;   // test 20 test

format logFormat( "%1%: %2% (%3%)" );
cout << logFormat % "error" % "test error" % 10 << endl;
cout << logFormat % "warnning" % "test warnning" % 20 << endl;

format 객체는 한번 만들고 여러번 사용할 수 있기 때문에 overhead도 그리 크지 않습니다.

----------------------------------------
http://moim.at
http://mkhq.co.kr

aswip의 이미지

케인 wrote:
저도 printf의 출력을 ostream으로 하고 싶어서 구현해 본 적이 있습니다. ^^;

http://hiya.byus.net/prog/archives/000063.html

기존에 제공되는 함수를 조합해서 만들고도 싶었습니다만, 중간에 버퍼를 이용해야 하는 제약 등이 있어서 포기하고, 직접 printf를 구현할 수 밖에 없었습니다.
가장 큰 장점은 사용중 어떠한 메모리 할당도 필요하지 않다는 것입니다.

vsnprintf와 같은 함수는 출력의 예상길이를 알기 위해서 사용할 수 있습니다. 길이를 알기 위해서 파싱할 필요까지는 없답니다. ^^;
(유닉스 계열에서는 snprintf를 사용하면 버퍼가 모자랄 경우 필요한 길이를 리턴합니다만, msvc는 그냥 0(에러값)을 리턴해버리지요. 하지만, %n 스펙을 사용해서 예상 길이는 알 수 있었던 것 같습니다. ^^;;)

boost 라이브러리에서는 좀 더 type safe한 방식으로 포멧팅 기능을 제공한다고 합니다만, printf와 같은 (위험하지만) 익숙한 인터페이스가 필요하기도 하지요. ^^;

%n Format 문자를 사용해서 예상 길이를 알수 있다. <= 이부분에 대한 보충 설명 부탁드려도 되겠습니까?

[ 보태기 ]

본의아니게 소스를 잠시 들여다 보았습니다. 대단하십니다. ^^ (짝짝짝)

- 인생은 스스로 -

케인의 이미지

생각해보니 %n을 사용하는데도 현실적으로 문제가 있더군요. -_-;
오래간만에 다시 떠올리다보니 약간 혼동이 있었나봅니다.

어쨌든 %n의 용법은 대략 다음과 같습니다. (snprintf 기준)

int len;
int rval;
rval = snprintf( NULL, 0, "test %s,%d\n%n", str, i, &len );
// %n을 만나는 순간까지의 출력 문자수를 포인터에 저장합니다.
// unix/linux라면 len == rval 입니다.

char* buf = new char[len+1];
snprintf( buf, len+1, "test %s,%d\n", str, i );

그런데 문제는 vsnprintf에 적용시 사용자의 va_list에 출력길이를 저장할 포인터를 추가할 수가 없다는 것입니다. 이 방법으로도 안되겠네요... msvc에서는 -_-;;

익명 사용자의 이미지

이렇게 간단한 포맷문 만들어 쓰고 있습니다...

string _stringFormat(char *pFormat, ... )
{
TCHAR *pBuffer = NULL;
UINT buffetLen = 0;

va_list va = NULL;
va_start(va, pFormat);
buffetLen = _vscprintf(pFormat, va) + 1;
pBuffer = (TCHAR*)malloc(buffetLen * sizeof(TCHAR));
ZeroMemory(pBuffer, (buffetLen * sizeof(TCHAR)));
vsprintf_s(pBuffer, buffetLen, pFormat, va);
va_end(va);

std::string sBuffer = pBuffer;

free(pBuffer);

return sBuffer;
}

LS)
string ss = _stringFormat(_T("Server %s [ START:%04d-%02d-%02d,%02d:%02d:%02d.%03d ]"), m_ChannelName.c_str(), CurTime.wYear, CurTime.wMonth, CurTime.wDay, CurTime.wHour, CurTime.wMinute, CurTime.wSecond, CurTime.wMilliseconds);

댓글 달기

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