(C/C++) print를 하거나 문자열을 리턴하는 함수. 어떻게 구성하는게 좋은가? - string을 리턴하자.
보통 “input은 인자, output은 리턴”으로 작성하는 편이 알아보기 쉽기 때문에 함수 인자로 포인터를 넘겨 이 포인터에 쓰는 방식으로 간접적으로 값을 되돌려 주는 것 보다, 명확하게 어떤 값을 리턴하도록 작성하는 편이다.
그러나 C같이 garbage collection이 안되는 언어에서는 어떤 값을 리턴하도록 작성하면 여러가지 이슈를 겪는 경우가 있다.
*** 또한 GC가 안되는 언어에서는 in/output을 명확히 작성한다 해도 반드시 순수 함수라고는 볼 수 없다. 함수 내부에서 malloc()
등을 호출하면 side-effect이 생기기 때문.
예를 들면 다음과 같은 상황이다.
malloc() : 문제점 - 바깥에서 free()
1
2
3
4
5
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;
}
지역 변수로 쓰는건 당연히 안되니, 보통은 두 가지 방법을 생각하게 된다.
- 안에서
malloc()
하고 이를 리턴하거나, - 함수 argument에
char\*
를 하나 더 받아서 간접적으로 돌려주거나.
위 코드 같은 경우 1번 케이스에 해당하는데, C는 GC가 안되므로 이렇게 작성하면 상위 함수에서 반드시 free()
해주어야 한다는 부담이 있다.
static __thread : 문제점 - Reentrancy
1번 케이스를 static \__thread
을 사용해 다음과 같이 바꿀 수 있으나 이 것도 좋은 방법은 아니다.
1
2
3
4
5
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;
}
*** \__thread
를 붙여주는 이유는, static
변수는 전역적으로 1개만 존재하기 때문에 멀티 스레드 환경에서 safe하지 않기 때문이다. lock을 걸기에는 너무 비싼 경우 사용한다. *** \__thread
지시어를 붙여주면해당 변수 또는 객체를 스레드 마다 독립적으로 갖게 된다.
*** 그래서 생성자가 있는 객체에는 붙일 수 없다.
* 주의1 )static
변수의 크기를 지정하는 값 c PATH_LEN
은 반드시 #define
이나 15
같은 상수여야 한다. static const PATH\_LEN
로 선언되어 있는 경우 ` Storage size of ‘ret_str’ isn’t constant` 에러가 발생한다.
thread-safe하게 만들었고, 바깥에서 free()
해야 하는지 마는지를 알 수 없는 문제도 해결했다. 그러나 static
변수 포인터를 반환하기 때문에 Reentrancy 문제가 발생한다.
1
2
3
4
char* a = parse_something("kkk");
char* b = parse_something("ttt");
printf("%s %s\n", a, b);
>>> /aaa/ttt/ccc /aaa/ttt/ccc
이처럼 a
변수나 b
변수나 결국 static
변수를 가리키기게 되고, 여기에는 제일 마지막에 호출된 c parse_something()
함수의 결과만 들어있기 때문에 ` a변수의 값을 사용하기 위해서는
c memcpy()`를 써서 값을 옮겨두어야 한다는 번거로움이 존재한다.
이처럼 reentrancy 문제를 가지고 있는 함수들은 ether\_aton(), inet_ntoa()
등이 있으며, 재진입 문제를 해결한 버전으로 ether\_aton\_r(), inet_ntop()
등이 있으니 이 쪽을 사용하는 편이 좋다.
포인터로 넘기기 : C에서의 해결책.
1
2
3
void parse_something(const char* dev, const char* ret_str) {
snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);
}
thread-safety, 바깥에서 free()
해야 하는지 알 수 없는 문제도 해결, reentrancy 문제도 해결. 이 경우 바깥에서 힙에 안잡고 지역변수로 스택에([]) 잡으면 아예 free()
를 하지 않아도 된다. (이 경우 포인터 변경은 불가능하기 때문에 copy 스타일의 작업만 가능하다는 제약이 있긴 하다.) 그래서 이런 경우 포인터로 넘기는 식으로 짜는게 여러 귀찮은 문제를 피할 수 있다.
[C++] string을 return해도 괜찮다.
C++의 경우 스마트 포인터를 사용하거나, STL을 사용하면 해결할 수 있다.
std::string
을 리턴하면, delete 부담도 해소되고 코드가 깔끔해진다는 장점이 있다.
단점은 string
을 리턴하면서 deep copy가 발생해서 느릴 것 같다는 점인데 요즘 컴파일러는 RVO(Return Value Optimization) 을 잘 해주기 때문에 알아서 포인터 인자로 받아서 간접적으로 넘기는 것이나 크게 차이 없는 정도로 최적화해준다. 그래서 그냥 리턴해줘도 된다. 이런 부분은 컴파일러가 알아서 해준다고 믿는 것이 좋다.