C++ String에 대해서는 이전 글 참고

 

이번에는 Java다.

 

Java String은 익히 알려진것처럼 UniCode 사용을 예상하고 작업되어 각 문자 1개가 2바이트씩 차지하고, 문자열을 수정시에는 문자열 자체가 수정되는것이 아니라 수정된 문자열이 새로 생성되는 등의 특징이 있다.

하지만 '체감상' Java String이 C++ String보다 다루기 편했던것으로 기억하는데, 오랫만에 한번 뜯어보도록 하자.

 

이번 레퍼런스 자료는 Open JDK의 소스코드입니다.

검색하면 금방 찾으실 수 있으니 링크는 딱히 달지 않겠습니다.

 

2. Java String의 경우

 

    public String(char value[]) {
        this(value, 0, value.length, null);
    }

 

Java String 또한 Char형 배열로 이루어져 있으며 (* C++과는 다르게 포인터 개념을 사용할 수 없기에 일반 배열이다), Java에서는 Char형의 바이트 값이 2바이트 이므로, String의 내부 구현또한 2바이트 문자열을 저장하는것으로 이루어진다.

어찌보면 당연한 소리인데... 배열의 각 값이 2바이트면 전체가 2바이트 데이터의 배열 구조인것은 당연한 소리 아니겠는가..

 

여기서 바로 차이점이 나타난다.

Java String의 경우 처음 값이 정해져버리기 때문에, 사이즈 변환에 좀 심한 에로사항이 생기게 된다. C++의 경우에는 (아무튼) 배열 포인터로 구현이 되어있으니, 값을 늘리는것 만큼은 (메모리 침범이 일어나지 않는다는 전제하에) 쉽게 늘릴 수 있으나...

 

그리고 또한, Java String은 기본적으로 'UTF-16' 인코딩을 사용하고 있다.

 

참고중인 Open JDK의 String.java 파일에 보면...

 

A {@code String} represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs. 

(see the section Character.html#unicode">Unicode haracter Representations in the {@code Character} class for
more information).

 

(Surrogate pairs 에 대해서는 나중에 따로 다루도록 하겠습니다. 저도 조금 이해할 필요가 있어보이네요)

 

아무튼 UTF-16 포멧으로 표현된다고 나타나있다.

UTF-8과 다른점은, 뭐 당연히 표현 비트 수가 8비트 / 16비트라는 차이점이라는것... 정도겠다.

 

 

아래부터는 String str = "Hello World";  라는 String을 선언해둔것으로 가정하고 시작한다.

* 또한, 기본적으로 세부 자료형은 StringUTF16임을 명시해둔다.

1) 접근법

(1) charAt(N);

C++ String의 at과 기능상 동일하다. N의 위치에 존재하는 값을 리턴해주는 메소드로, 내부적으로는 String Data Array(= byte 배열) 과 int N 값을 인자로 받고, 이를 getChar() 메소드에서 탐색하고 리턴해주는 방식이다.

 

그래서 getChar()는 어떻게 되어있느냐...

 

    static char getChar(byte[] val, int index) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }

이런식으로, byte배열에서 index에 맞는 위치의 값을 bit shift를 통해 리턴해주는 모습을 보여준다.

Java에서는 Char형도 2바이트로 표현하니, 바이트 값이 2개가 올라가는것을 볼 수 있다.

 

 

(2)str.indexof(temp);

temp 라는 문자가 어디에 존재하는지 탐색해주는 메소드이다.

내부 구현은, indexof가 호출시, 호출된 String의 구조가 latin1 (잠깐 찾아보니 그냥 아스키코드값하고 동일하다.) 인가, 아닌가로 비교를 실시한다.

만일 latin1 형식이 아닌경우, StringUTF16라는 클래스의 indexof 메소드로 넘어가게되는데, 일반 구조의 경우에는 'getChar()' 를 호출하여, 문자열의 위치를 탐색하고 값을 리턴해주는 방식으로 이루어진다.

그런데 재밌는것이...

 

해당 메소드의 구현 밑바닥까지 가면

    @HotSpotIntrinsicCandidate
    private static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
        checkBoundsBeginEnd(fromIndex, max, value);
        return indexOfCharUnsafe(value, ch, fromIndex, max);
    }

    private static int indexOfCharUnsafe(byte[] value, int ch, int fromIndex, int max) {
        for (int i = fromIndex; i < max; i++) {
            if (getChar(value, i) == ch) {
                return i;
            }
        }
        return -1;
    }

'Unsafe' 라는 문구가 붙어있는것을 확인 할 수 있다.

범위 침범을 할 수 있어서 그런건지, 아니면 다른 이유가 있는건지는 모르겠지만 구현시에 '사실 좀 위험하긴한데...' 라는 생각을 했던건 아닌가 싶다.

 

2) 길이 파악

length(); 라는 메소드가 존재한다.

 

근데 구현이... 내가 잘못본게 아니라면

    public static int length(byte[] value) {
        return value.length >> 1;
    }

이게 끝이다.

배열값을 input으로 받고, 배열의 길이를 1 shift (뒤의 '\0' 문자 제거용으로 추정)하여 리턴해준다.

 

더 찾아봐도 저거 외에는 제대로 된 메소드를 찾을수가 없었다...

 

 

3) 비교

Java String에서는 '==' 를 쓰면 안된다는 사실을 알것이다.

==를 사용하게 되면, 값 자체를 비교하는것이 아니라 값의 위치, "객체"가 동일한지 아닌지를 비교하기 때문에 무조건 false가 날 수 밖에 없다.

그래서 사용하는것이 equals인데...

    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            int len = value.length >> 1;
            for (int i = 0; i < len; i++) {
                if (getChar(value, i) != getChar(other, i)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

구조는 다음과 같다.

1)) 길이를 비교한다.

2)) 길이가 같다면 -> 동일한 위치의 문자를 1개씩 비교한다

3)) 이 중 하나라도 틀린다면 -> false

4)) 모든 비교를 통과했다면 true 

 

 

이런 식으로 구현이 되어있다.

 

아직 주제인 'C++ String 과 Java String의 차이' 에 대해서는 언급을 하지 않았는데, 이는 다음장에서 언급하도록 하겠다.

모 회사 면접 준비중에 예전에 당했던 문제가 생각나서 정리하려다, 문득 제대로 조사해본적이 없는거같아 기록용으로 작성한다.

사실, 검색해보다가 내가 시원하게 이해할만한 자료가 없었던것도 있고...

 

C에서 Char 형 배열 / 포인터로 겨우겨우 만들던 String에서 벗어나, 관리 함수등을 추가한 C++ String Class로 되면서, '어느정도는' 타 언어의 String 사용법과 비슷하게 이루어지기 시작했다. 물론, 문자열 다루는거는 Java 보다는 불편하긴한데, 이전 면접에서 '왜 불편한가요?' 라는 질문에 좀 어영부영 대답한 감이 없잖아 있는것같다.

 

따라서 포인터 개념이 살아있는 OOP인 C++과, 전부 GC로 관리되는 Java와 비교를 해보고자 한다.

 

 

1. C++ String의 경우 (wstring은 포함시키지 않음)

기본적으로 C++ String은 'Basic String' 이라는 자체 클래스를 내부에 구현한 상태로 되어있으며, Basic String의 생성자는 크게 'const Char *' 를 input으로 받는 경우, 자체 Allocator를 input으로 받는 경우(= 타 value_Type 배열포인터를 input으로 받는 경우), basic_string 참조값을 input으로 받는 경우로 나뉘어진다.

즉, 실제 구현에서는 '일단 모든 데이터형을 받을수 있도록' 구현이 되어있긴 하나, 문자열 자체를 받는경우는 여전히 Char 형 배열 포인터를 값으로 받는다... 고 설명할 수 있다.

그리고, String Class가 되면서, 각종 접근법 / 용량 체크 / 복사 / equal 등의 함수가 가능해졌다.

 

 

아래부터는 String str = "Hello World";  라는 String을 선언해둔것으로 가정하고 시작한다.

1) 접근법

str.at(N) = N번 자리의 문자열을 리턴해준다.

str.at(1) 을 사용시, 1번째 값인 e를 리턴해주게 된다.

 

str[N] = N번 자리의 문자열을 리턴해준다.

str.at(N)과 다른게 뭐냐고 한다면, 둘다 결국엔 str[N]으로 귀결되는건 동일하나,

basic_string<_CharT, _Traits, _Allocator>::at(size_type __n) const
{
    if (__n >= size())
        this->__throw_out_of_range();
    return (*this)[__n];
}

at()의 경우에는 잘못된 값 삽입 시 out of range() 경고를 리턴해주는 차이가 있다.

속도 자체는 자체 함수로 이동하지 않는 str[N]이 빠르긴 하나, 내가 짠 코드를 100% 믿을 수 있는게 아니라면 at()을 쓰는것을 추천한다...

 

 

2) 길이 파악

size() 함수와 length() 함수가 존재하는데...

 

    _LIBCPP_INLINE_VISIBILITY size_type size() const _NOEXCEPT
        {return __is_long() ? __get_long_size() : __get_short_size();}
    
    _LIBCPP_INLINE_VISIBILITY size_type length() const _NOEXCEPT {return size();}
    

그냥 length 함수 호출해도, size를 호출해도 결국엔 size() 함수가 호출되는거니까 아무거나 써도 된다 (...)

 

 

3) 비교

str.compare() 이라는 함수가 있다.

str.compare("ABC") 와 같은 방식으로 사용이 가능한데...

 

일반적으로, '같으면 0', '함수를 호출한 String이 사전순으로 빠를때 -1', '사전순으로 느릴때 1' 의 값을 리턴하는것으로 알고 있을것이다.

 

이는 또 재밌게도, 

basic_string<_CharT, _Traits, _Allocator>::compare(const _Tp& __t) const
{
    __self_view __sv = __t;
    size_t __lhs_sz = size();
    size_t __rhs_sz = __sv.size();
    int __result = traits_type::compare(data(), __sv.data(),
                                        _VSTD::min(__lhs_sz, __rhs_sz));
    if (__result != 0)
        return __result;
    if (__lhs_sz < __rhs_sz)
        return -1;
    if (__lhs_sz > __rhs_sz)
        return 1;
    return 0;
}

'일단' 두 String을 가져와 min 비교를 걸친 후 (String 별 호출순서 비교), 만일 min() 비교가 동일한것으로 나오면, 길이 비교를 진행, 이후 해당 값을 리턴해주는 식으로 진행된다.

즉, 만일 str.compare("Hello"); 를 넣어줬다면

Hello World / Hello 두 문자열의 비교 자체는 4번째 값인 o까지 비교를 하고, 그 이상은 비교가 불가능하니 if문으로 넘어가게 된다.

근데 여기서 좌측값 (= 함수를 호출한 String)의 길이가 더 길기때문에, 1을 리턴해주는것으로 결과가 종료되는것이다.

 

Min의 내부 구현은 (아스키코드 값의 경우) A값과 B값의 코드 순서로 비교를 수행하게된다.

자세한건 검색을 통해 찾아보시길...

 

 

 

아무튼, 요런 식으로 구현이 되어있는데, 위에 언급했다시피, '배열 포인터' 로 구현이 되어있기에, 문자열을 수정하면 해당 문자열 메모리 자체를 수정하는식으로 값을 수정하게 된다.

이것이 메모리 관리  관점에서 Java String과의 가장 큰 차이점이라고 볼 수 있기도 하다.

 

나머지는 다음장에... 생각보다 길어질거같아서 쪼개야할것같다.

 

 

 

더보기

https://code.woboq.org/llvm/libcxx/include/string.html

 

string source code [libcxx/include/string] - Woboq Code Browser

 

code.woboq.org

참고자료.

C++ 내부 구현을 기록해 둔 레퍼런스 사이트.

해당 글을 작성할때 참고한 페이지를 링크해두었다.

 

초보자가 볼 만한 사이트는 아니므로, 레퍼런스 자료가 필요하면 https://en.cppreference.com/를 참고하거나, 검색을 통해 한글로 설명된 다른 페이지를 참고하는걸 추천드린다..

 

(기억을 더듬어가며 쓰는거라 설명이 정확하지 않을 수 있습니다. 참고자료들을 봐주세요.)

 

오랫만에 잡설 하나 적어둔다.

 

float 등 부동소수점 변수 연산을 하다보면, 처음에는 실수로 var1 == var2를 했다가 틀려먹는 경우가 잦아, 이를 확인해보면 소수점 저 밑 끝자리에 내가 적지도 않은 이상한 값이 묻어있는 경우가 종종 보이곤 한다.

 

해서, 검색하다보면 비교시에 (var == 0.0f) 가 아니라 (* 이게 되는 언어도 있긴하다) 다른 방법을 쓰라고 하는 경우가 많고, 이 어처구니없는 상태를  해결하고자 찾다보면 'epsilon' 이라는 변수/함수 등이 보이게 된다.

Epsilon Delta가 필요한 이유 . JS

 

Epsilon Delta, Epsilon은 일반적인 사용으로는 '수치 범위' 를 지정하기 위한 예약변수로 쓰기도 하는데,

 

보통은 ( var < Epsilon && var > -Epsilon )  와 같은 형식으로 비교하는 모습으로 쓰곤 한다.

 

이는, 부동소수점 연산의 특이사항때문에 사용하는것으로,

 

각 부동소수점 표현 포멧에 따라 '안전이 보장되는' 길이가 달라지는것을 인지하고, 이에 피해를 입지 않고자 (해당 증상을 '막는것'은 거의 불가능하다. 그럼 정밀도를 올려야하는데... 정밀도가 올라가면 연산 속도가 주르륵 떨어진다...) 추가하는 일종의 '상대 오차 반올림 값' (relative error unit roundoff, 일반적으로 쓰는 표현은 아닙니다.) 으로, 값을 보장할 수 없는 부분에서 반올림 등으로 근사값 처리를 해주기 위함입니다.

 

보통은 이를 프로그래머가 직접 만들기도 하지만 (만일 소수점 3번째 자리까지만 쓴다 => 4번째 자리나 5번째 자리에서 반올림 하겠다 등등?) 언어에 따라 이미 해당 값을 리턴해주는 함수가 존재하기도 합니다.

 

C++의 경우, limits 헤더의 numeric_limits 템플릿 클래스 안에 epsilon() 이라는 함수로 존재하며, 목적에 맞게 '거의 비슷한' 값을 비교 시에 사용하는 예시로 올라와 있습니다.

 

 

따라서...

 

이 값들을 이용함으로써 부동소수점 변수들에 대해 의미없는 오차 및 발생할 수 있는 문제들을 미연에 방지할 수 있게 된다... 는 점이 주요 포인트.

 

보통 부동소수점 연산이 많은 프로그램들에 경우에는, eplison delta 값을 이용하여 자체적으로 값을 비교해줄 수 있는 함수를 만들어 쓰곤 하는 편이다.

 

그게, 매번 ( var < Epsilon && var > -Epsilon ) 처럼 지저분하게 쓰는것보다는 좀 더 안전하고, '깔끔하게' 보일 수 있으니까. 

 

 

 

 

 

 

 

 

* 참고자료

1) https://en.wikipedia.org/wiki/(%CE%B5,_%CE%B4)-definition_of_limit

2) https://en.wikipedia.org/wiki/Machine_epsilon

3) https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

4) https://gigglehd.com/gg/hard/5427559

개발중인 오픈소스 프로그램이 하나 있다.

 

파일 리스트가 담긴 리스트 파일과, 해당 리스트와 매칭되는 '실제 파일' 을 비교하여 리스트 파일을 수정해주는 프로그램인데...

 

이를 1년전쯤에 만들었다가 면접에서 구조 지적을 한번 받고나서 (C++인데 왜 C 스타일로 짰느냐...) 리팩토링 작업중이었다.

 

프로그램의 중요한 부분 중 하나인, '리스트 파일을 읽어오는 함수' 를 재작업하는데 자꾸 프로그램이 다운되는 현상이 있어 문제를 체크해보다가... eof 체크도 넣어보고 했는데.

 

원인은 좀 다른곳에 있었다.

 

 

(표준 출력부가 존재하는 이유는 당연히 테스트용...)

 

요게 그 문제의 함수부.

 

string 변수인 tempinput에 파일 1줄을 읽어 저장하고, 해당 파일 라인에 'name' 이라는 키워드가 있는지 검사를 하는 구조이다.

 

name 키워드가 있어야 아래의 MidData 및 Convert가 작동을 하는데,

구조를 다시 생각해보니

File line Read -> npos Check -> (if true) Convert 의 구조로 진행되어야할것이

npos Check -> (if true) File line Read -> Convert로 돌아가고있는게 아니었는가.

 

그러니 자꾸 터지지...

오류를 수정하면서 모양도 조금 다듬었다. 

 

좀 이런 멍청한 실수는 안해야되는데... 참... 

 

밤이라 피곤해서 그랬다고 생각하자... 

'프로그래밍 > C/C++' 카테고리의 다른 글

포인터 변수 사용시 데이터 오염 문제  (0) 2020.11.28
Const 키워드.  (0) 2020.08.18
Visual C++에서 C++ 버전확인하는 방법.  (3) 2020.02.27
기록 #1  (0) 2020.01.18
Jsoncpp 라이브러리 사용 관련  (0) 2020.01.01

이전에 만들어 둔 프로젝트를 리빌딩 하다가 발견한것.

 

 

* 기본적으로 C++에서 현재 C++ 표준 버전을 확인하는건 이미 예약된 __cplusplus 매크로로 가능한것으로 알려져있다.

 

하지만 뭐가 문제인건지, Visual Studio에서 C++를 사용시, 현재 버전을 몇으로 설정하던 무조건 199711 (=C++98)로만 나온다.

 

검색해보니, 이것에 대한 원인은 못찾았고, 대신 해결방안을 찾았다.

 

https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=vs-2019

 

/Zc:__cplusplus (Enable updated __cplusplus macro)

/Zc:__cplusplus (Enable updated __cplusplus macro) In this article --> The /Zc:__cplusplus compiler option enables the __cplusplus preprocessor macro to report an updated value for recent C++ language standards support. By default, Visual Studio always ret

docs.microsoft.com

해당 페이지에서처럼, /Zc:__cplusplus 명령어를 추가해주면, __cplusplus 매크로가 실제 버전에 맞게 잘 작동한다는 것이다.

 

자 그래서. 이걸 어디다 집어넣어야 하는가.

 

사용중인 Visual Studio가 한글버전이어서 살짝 헤멨는데, 이걸 검색해서 보는 분들은 조금이나마 덜 헤메시라고 남겨둔다.

 

 

1. 프로젝트 > 프로젝트 속성 클릭

맨 아래의 스패너모양을 누르면 된다.

 

2. 속성 > C/C++ > 명령줄 (=Commend Line)으로 이동.

 

3. 명령줄에 '/Zc:__cplusplus' 입력

그냥 뭐 이런식으로 집어넣으시고 적용누르시면 된다.

 

4. 체크

적용전

 

적용후 1 C++ 버전이 C++17로 인식되어 if문 아래의 구문이 활성화되었다.
적용후 2 C++ 버전을 14로 맞추었을때, C++14로 정상인식된다.

 

언젠가 좀 써먹을일이 있을거같아 기록해둔다.

'프로그래밍 > C/C++' 카테고리의 다른 글

포인터 변수 사용시 데이터 오염 문제  (0) 2020.11.28
Const 키워드.  (0) 2020.08.18
오늘의 실수.  (0) 2020.03.29
기록 #1  (0) 2020.01.18
Jsoncpp 라이브러리 사용 관련  (0) 2020.01.01

 

호출스택 뒤져보는걸 본격적으로 하는건 처음인듯.

 

지금까진 안해봤냐구요?

 

코드 길이가 짧아서 호출스택을 코드에서 직접 찾을수 있었거든요...

참고 블로그 : https://wergia.tistory.com/184

 

[Unity3D] UI 비법서 (4) - UI 개발자라면 제발 Rect Transform 애용합시다!

UI 비법서 (4) - UI 개발자라면 제발 Rect Transform 애용합시다! 작성 기준 버전 :: 2019.1.4f1 유니티 개발을 처음으로 공부하는 개발자들은 기본적으로 게임 오브젝트의 위치를 이동시키는 코드를 작성하려고..

wergia.tistory.com

 

 

UI 컴포넌트 간 위치 지정시 일반 Transform 좌표가 아닌 Rect Transform 좌표를 사용할것.

 

Rect의 기준점으로 사용될수 있는건 4가지 데이터가 있는데, Up, left, right, bottom 4가지.

 

Up / bottom은 y축 값, left, right는 x축 값이며,

 

up, right 는 offsetMax, bottom, left는 offsetMin값이다

 

즉, rect transform의 bottom이 필요한 경우

 

var rect = (rect 정보를 가져올 오브젝트).GetCompo.GetComponent();

foo(rect.offsetMin.y);

var rect = (rect 정보를 가져올 오브젝트).GetCompo.GetComponent();
foo(rect.offsetMin.y);

 

식으로 획득이 가능하다.

'게임 개발 > Unity' 카테고리의 다른 글

짧은 근황  (0) 2021.06.13
기록 #2  (0) 2020.05.09
분명 자주 쓰는데 자주 까먹는 기능들  (0) 2019.12.27

당연하게도 세팅을 위해서 라이브러리 추가를 해줘야 한다고 생각해서,

VS 프로젝트를 생성한 이후 추가메뉴를 찾다가 까먹어버린바람에...

'근데 얘들 기본 SDK로 들어왔으니까 있지 않을까?' 하고 바로 include를 시도해봤는데...

 

 

 

아니 이게 왜 다 있어?

 

DX10부터 12까지 다 있네요?

 

음...

 

아니 이러면 진짜 설치할 필요가 없었던거 아니었나? 

+ Recent posts