보통 "input은 인자, output은 리턴"으로 작성하는 편이 알아보기 쉽기 때문에 함수 인자로 포인터를 넘겨 이 포인터에 쓰는 방식으로 간접적으로 값을 되돌려 주는 것 보다, 명확하게 어떤 값을 리턴하도록 작성하는 편이다.

 

그러나 C같이 garbage collection이 안되는 언어에서는 어떤 값을 리턴하도록 작성하면 여러가지 이슈를 겪는 경우가 있다.


*** 또한 GC가 안되는 언어에서는 in/output을 명확히 작성한다 해도 반드시 순수 함수라고는 볼 수 없다. 함수 내부에서 ``c malloc()`` 등을 호출하면 side-effect이 생기기 때문.


예를 들면 다음과 같은 상황이다.


malloc() : 문제점 - 바깥에서 free()

```c

char* parse_something(const char* dev) {

    char* ret_str = (char*)malloc(PATH_LEN);

    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);

    return ret_str;

}

```

지역 변수로 쓰는건 당연히 안되니, 보통은 두 가지 방법을 생각하게 된다. 

1. 안에서 ``c malloc()``하고 이를 리턴하거나,

2. 함수 argument에 ``c char*``를 하나 더 받아서 간접적으로 돌려주거나.


위 코드 같은 경우 1번 케이스에 해당하는데, C는 GC가 안되므로 이렇게 작성하면 상위 함수에서 반드시 ``c free()`` 해주어야 한다는 부담이 있다.


static __thread : 문제점 - Reentrancy

1번 케이스를 ``c static __thread``을 사용해 다음과 같이 바꿀 수 있으나 이 것도 좋은 방법은 아니다.

```c

char* parse_something(const char* dev) {

    static __thread char ret_str[PATH_LEN];

    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);

    return ret_str;

}

```

*** ``c __thread``를 붙여주는 이유는, ``c static`` 변수는 전역적으로 1개만 존재하기 때문에 멀티 스레드 환경에서 safe하지 않기 때문이다. lock을 걸기에는 너무 비싼 경우 사용한다.

*** ``c __thread`` 지시어를 붙여주면 해당 변수 또는 객체를 스레드 마다 독립적으로 갖게 된다.

*** 그래서 생성자가 있는 객체에는 붙일 수 없다. 


* 주의1 ) ``c static`` 변수의 크기를 지정하는 값 ``c PATH_LEN``은 반드시 ``c #define``이나 `` 15``같은 상수여야 한다.

``c static const PATH_LEN`` 로 선언되어 있는 경우 `` Storage size of ‘ret_str’ isn’t constant`` 에러가 발생한다.


thread-safe하게 만들었고, 바깥에서 ``c free()``해야 하는지 마는지를 알 수 없는 문제도 해결했다.

그러나 ``c static`` 변수 포인터를 반환하기 때문에 Reentrancy 문제가 발생한다.

```c

char* a = parse_something("kkk");

char* b = parse_something("ttt");

printf("%s %s\n", a, b);

>>> /aaa/ttt/ccc /aaa/ttt/ccc

```

이처럼 `` a`` 변수나 `` b``변수나 결국 ``c static`` 변수를 가리키기게 되고, 여기에는 제일 마지막에 호출된 ``c parse_something()`` 함수의 결과만 들어있기 때문에 `` a`` 변수의 값을 사용하기 위해서는 ``c memcpy()``를 써서 값을 옮겨두어야 한다는 번거로움이 존재한다.


이처럼 reentrancy 문제를 가지고 있는 함수들은 ``c ether_aton(), inet_ntoa()`` 등이 있으며, 

재진입 문제를 해결한 버전으로 ``c ether_aton_r(), inet_ntop()`` 등이 있으니 이 쪽을 사용하는 편이 좋다.


포인터로 넘기기 : C에서의 해결책.

```c
void parse_something(const char* dev, const char* ret_str) {
    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);
}
```

thread-safety, 바깥에서 ``c free()``해야 하는지 알 수 없는 문제도 해결, reentrancy 문제도 해결.

이 경우 바깥에서 힙에 안잡고 지역변수로 스택에([]) 잡으면 아예 ``c free()``를 하지 않아도 된다. (이 경우 포인터 변경은 불가능하기 때문에 copy 스타일의 작업만 가능하다는 제약이 있긴 하다.)

그래서 이런 경우 포인터로 넘기는 식으로 짜는게 여러 귀찮은 문제를 피할 수 있다. 



[C++] string을 return해도 괜찮다.

C++의 경우 스마트 포인터를 사용하거나, STL을 사용하면 해결할 수 있다.


``cpp std::string``을 리턴하면, delete 부담도 해소되고 코드가 깔끔해진다는 장점이 있다.


단점은 `` string``을 리턴하면서 deep copy가 발생해서 느릴 것 같다는 점인데

요즘 컴파일러는 RVO(Return Value Optimization)을 잘 해주기 때문에 알아서 포인터 인자로 받아서 간접적으로 넘기는 것이나 크게 차이 없는 정도로 최적화해준다. 그래서 그냥 리턴해줘도 된다. 이런 부분은 컴파일러가 알아서 해준다고 믿는 것이 좋다.



'Languages & Frameworks > C C++' 카테고리의 다른 글

Qt 로 빌드한 바이너리 배포하기  (0) 2019.02.22
Effective Modern C++  (0) 2019.01.22
More Effective C++  (0) 2019.01.22
Effective C++  (0) 2019.01.22
[C++] operator overloading  (0) 2019.01.22