C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
분야별 포럼
C++빌더
델파이
파이어몽키
C/C++
프리파스칼
파이어버드
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

자유게시판
세상 살아가는 이야기들을 나누는 사랑방입니다.
[10641] 오묘한 AnsiString
김상구.패패루 [peperu] 2255 읽음    2005-06-17 18:19
세더군한테 배운겁니다. ㅎㅎㅎ 왜 전 이 생각을 못했는지...

다들 아래와 비슷한 코드들은 많이 쓰실 줄로 믿습니다.

String sStr1 = "12345", sStr2 = "67890";
char buf[4096];
sprintf(buf, "%s %s", sStr1.c_str(), sStr2.c_str());

아주 자연스럽고 당연해 보이죠.... 그럼 다음 코드를 보시죠. 바뀐 부분만 적을께요. 맨 마지막 줄입니다.

sprintf(buf, "%s, %s", sStr1, sStr2);

잘 됩니다. 잘 될 수밖에 없습니다. AnsiString에 존재하는 유일한 맴버변수는 char* 타입의 Data 밖에 없기 때문입니다.
고마버 세더군~~~

지금부터의 내용은 아시는 분은 아시고 모르시는 분은 모르는 그런 내용입니다. 알면 상식, 모르면? 지식~~
이 내용들은 Q&A나 각종 팁에서 한번쯤을 다뤄졌으리라 생각은 합니다만... 저처럼 그냥 지나쳐가신 분들을 위해 함 써 보죠.

AnsiString은 쓰면 쓸 수록 정말 잘 만든 클래스라는것을 실감합니다.
AnsiString의 동작특성을 잘 모를땐 AnsiString타입을 반환하는 함수를 만들지 않기 위해 온갓 삽질을 했는데... 다 쓰잘데기 없는 짓이었죠... 결론은 안심하고 반환해도 됩니다. 오히려 주의해야 할 것은 다른 부분이었습니다.

AnsiString의 각 char 요소를 접근하는 방법은 두가지입니다.
위의 예제를 계속 이어가보죠.

char cFirstChar = sStr1[1];
if (cFirstChar == sStr1.c_str()[0])
  ShowMessage("Same Character!!!");

보시는 바와같이 첫번째는 [] 연산자를 이용하는 법... 1base이죠
c_str()을 통해 char* 을 얻고 이를 배열로 접근하는 방법... 0base입니다.
다들 아시는 내용이죠?

이번엔 좀 다른 실험을 해 보죠.

sStr1 = sStr2;
char *pChar = sStr1.c_str();
*pChar = 'A';
ShowMessage(sStr1);
ShowMessage(sStr2);

어떤 결과가 화면에 나올까요?
답은...

둘 다
A7890 이 나옵니다.

왜냐... sStr1 = sStr2를 실행할 때.. AnsiString은 문자열 복사를 하지 않기 때문입니다. 둘 다 동일한 포인터를 가리키게 만든 후 RefCount를 하나 증가시키기만 하기 때문이죠.

좀 더 이어서 테스트를 해 보죠.

sStr1[1] = 'B';
ShowMessage(sStr1);           // "B2345" 출력
ShowMessage(sStr2);           // "A7890" 출력
서로 다른 결과를 보게 되실겁니다.
즉.. []연산자를 쓰는 순간... sStr1은 sStr2와 완전 분리됩니다. 메모리 복사를 이 때 한다는 얘기죠.
필요할 때 메모리 카피를 한다... 요는 이겁니다.
멋지지 않습니까....

AnsiString을 반환하는 함수를 만들어도 메모리복사때문에 오버헤드가 걸릴 것을 우려할 이유는 전혀 없다는겁니다.
함수 인자로 AnsiString을 넘기는 경우에도 메모리 복사를 피하기 위해
const AnsiString &Value 이딴식으로 넘기지 않아도 크게 오버헤드가 걸리지는 않을거라는거죠.
물론 Reference Count를 증감시키기 위해 StringProxy는 테이블을 뒤질겁니다. 당근 hash로 되어 있겠죠. 오버헤드 거의 없다고 봐야죠. 적어도 전 이번에 AnsiString을 VC++에서 쓸 수 있도록 만들면서 그렇게 구현했습니다.

이런 특성은 보통은 문제를 일으키지 않지만... c_str()로 얻은 char* 포인터를 이용해서 값을 바꾸는 경우.. 예상치 않은 문제를 일으킬 가능성은 분명 존재합니다. AnsiString이 가진 힘에 대한 댓가겠죠. 이럴땐 Unique함수를 쓰시면 됩니다.
불편한가요? 하지만 이 특성을 잘 염두해두고 프로그램을 만든다면 잃을 것 보다는 얻는 것이 훨씬 크리라 확신합니다.

모두들 즐프 하시구요~~

다들 아시는 것 가지고 잘난척 한 것 같은 기분이 자꾸 드네요. 제발.. 나만 모르고 있던건 아니길.. ㅋㅋㅋ
남병철.레조 [lezo]   2005-06-17 20:20 X
국내 개발 현실을 생각컨데...
관심없는한 이런거 해보려고 생각하는 사람 별로 없을듯 합니다. ;;
특히 학생이 아니라 직업으로 개발을 하게되면 더더욱 -ㅋ-; 타성과의 싸움이;;

가끔(자주!) 올려주세요... 일에 치여도 이런글 읽으면 기분이 좋아지기도 합니다. ㅎㅎ

행복한 사람이 되는 방법중 하나가 때때로 이법에 대한 가르침을 듣는것 ... ... ... .
마술감자 [magicpotato]   2005-06-17 20:42 X
좋은 내용 올려주셨네요~ 보충 하나 합니다.

String sStr1, sStr2;
char buf[4096];
sprintf(buf, "%s %s", sStr1.c_str(), sStr2.c_str());
sprintf(buf, "%s %s", sStr1, sStr2);
는 서로 다른 결과를 갖습니다.

원칙적으로 AnsiString 인스턴스 자체를 char *에는 대입할수가 없습니다.
printf가 가변인자를 받기 때문에 형 검사가 없으므로 사실 억지로 되는거죠..
AnsiString의 멤버가 char * 한개 (sizeof(AnsiString) == 4)이기 때문에 우연히 가능하다고 보는게 좋은것 같습니다.

C언어의 하위레벨 관점에서 보면 사용해도 되지만, 언어론 측면에서 보면 c_str()를 이용하는것이 맞습니다.
수야!╋ [sooya23]   2005-06-17 21:13 X
감자 좋은 내용 땡큐~
홍환민.행복 [hhshhm]   2005-06-17 21:14 X
패패루님, 마술감자님. 예 두분다 맞는 말씀이네요. 볼랜드에서 아마 의도가 있어서 일부러 char * 형 멤버 하나만 둔게 아닐까 싶어요~
김상구.패패루 [peperu]   2005-06-18 08:44 X
C언어론 측면에서 c_str()을 써야 한다는 것에는 당연히 동의합니다만 서로 다른 결과가 나온다는건 틀린 내용이네요. 실제 해 보시면 동일한 결과가 나옵니다. 그리고 동일한 결과가 나올 수 밖에 없죠. 이유는 마술감자님이 지적하신 것 대로 printf등은 형검사가 없고 오로지 format 문자열에 지정된 타입을 참조하기 때문입니다.
저렇게 쓰는것이 좋다... 가 아니라 저렇게 써도 아무 이상없이 된다... 이겁니다. ^^ 선택은 C++라는 언어의 특성이 그렇듯... 개발자의 선택의 몫이죠.

그리고 저는 볼랜드가 이런 점을 이미 고려하고 있었다는 느낌을 많이 받습니다. 우연의 일치로 보이지 않네요. AnsiString이 RefCount를 이용한 이상 char* 캐스팅 오퍼레이터를 지원하는 것은 무척 위험한 발상이었고... 이런 문제들을 두루 고려한 설계라는 느낌을 많이 받게 됩니다.
마술감자 [magicpotato]   2005-06-18 13:14 X
초기값을 대입하지 않은 AnsiString은 멤버로 NULL을 갖고,
c_str() 호출시 멤버가 NULL이면 "" 을 리턴 합니다.
NULL과 주소가 있는 ""는 다르죠 ㅇ_ㅇ
김상구.패패루 [peperu]   2005-06-18 21:42 X
^^ 맞습니다. 그래서 Data의 값이 NULL인 문자열에 c_str()로 얻은 포인터로 0이아닌 다른 값으로 강제로 바꾸면.. 그다음부터 난리나죠.. ^^ 마술감자님의 지적이 정확합니다.
MAXIST [maxist]   2005-06-20 13:36 X
이미 한 번씩 언급 되었지만, 상기 논의의 위험성을 알리고자 부언합니다.

'동작하기 때문에 써도 된다'는 생각은 위험한 것입니다.

1. AnsiString에 char* 타입으로 형 변환 할 수 있는 오퍼레이터가 제공되지 않고, 2. char *를 반환하는 c_str()이라는 맴버가 존재한다

는 것은 char *이 필요할 때 꼭 c_str()을 쓰라는 의미지요.

String sStr;
char buf[512];
sprintf(buf, "%s", sStr);

마술감자님의 지적과 같이, 이 코드는 1. 가변 인자는 형 검사를 받지 않고
2. 이 class의 '첫 번째' 맴버가 '우연히' char *이기 때문에 작동하는 것입니다. class 제작자가 의도적으로 그리 한 것이 아닙니다.

OOP의 큰 관심사 중 하나는 '은닉'인데, 이렇게 되면 memeber에 (그것도 직접 접근하지 말라고 private으로 지정한 member에) '직접' 접근하는 꼴이 됩니다. class를 만드는 사람이 그렇게 쓰도록 설계할 리가 없지요.

'아무렴 어때'라고 생각할 수도 있겠지만, 결정적인 문제는 훗날 AnsiString의 구현이 '약간'이라도 변경되는 경우겠지요.  '인터페이스'는 놔두고 '구현'만 고칠 수 있는 것이 class니까요. char * 맴버 앞에 '아무 것이나' 하나만 추가 되어도 재앙이 일어날 것입니다.

sprintf(buf, "%s", sStr); 이렇게 써도 우연히 작동하지만, sprintf(buf, "%s", sStr.c_str());로 쓰자고 하는 것이 맞을 듯 싶네요.
에보니.^0^m [mortalpain]   2005-06-23 22:30 X
ㅎㅎ 오묘하넹 ㅡ0ㅡㅋ

+ -

관련 글 리스트
10641 오묘한 AnsiString 김상구.패패루 2255 2005/06/17
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.