반응형

C++에서 map<key, value>를 쓰다 보면, value를 포인터로 관리해야 하는 경우가 있다. 예를 들어, 아래와 같은 형태의 코드이다.


#define <map>

using namespace std;


class Foo {

public:

    int a;

    double b;

    Foo(int a, double b){

        this->a = a;

        this->b = b;

    }

}


int main() {

    std::map<int, Foo*> fooMap;

    fooMap[101] = new Foo(10, 505.26);

    fooMap[104] = new Foo(8, 1233.61);

    // ...



    fooMap.erase(101);

    fooMap[105] = new Foo(17, 564.98);

    // ...


    fooMap.clear();

    return 0;

}


위와 같이 실행하면 메모리 누수가 발생한다. map.erase(key)를 이용해서 특정한 pair를 삭제하더라도 포인터를 통해 할당되어 있는 메모리 영역은 자동으로 해제(delete)되지 않는다. 즉, 명시적으로 코드 어디선가 new를 통해 오브젝트를 할당했으면, map의 value로 쓰이는지 여부에 관계 없이 명시적으로 delete를 해 주어야 한다.


실제로 C++의 erase와 clear 함수의 소스코드 위에 달려 있는 주석에도 같은 내용이 명시적으로 쓰여져 있다.


map erase 함수 주석:

  /**
   *  @brief Erases elements according to the provided key.
   *  @param  __x  Key of element to be erased.
   *  @return  The number of elements erased.
   *
   *  This function erases all the elements located by the given key from
   *  a %map.
   *  Note that this function only erases the element, and that if
   *  the element is itself a pointer, the pointed-to memory is not touched
   *  in any way.  Managing the pointer is the user's responsibility.
   */

...


map clear 함수 주석:

  /**
   *  Erases all elements in a %map.  Note that this function only
   *  erases the elements, and that if the elements themselves are
   *  pointers, the pointed-to memory is not touched in any way.
   *  Managing the pointer is the user's responsibility.
   */

...



포인터를 명시적으로 delete하는 것이 번거롭다면 C++11 이후의 버전들이 smart pointer를 제공하는데, (또는 이전 버전의 경우 boost::shared_ptr) 이것을 사용하면 자동으로 메모리 할당을 해제해 주므로 명시적인 delete를 할 필요가 없다.


포인터에 대해서 더이상 참조당하고 있지 않을 경우에 자동으로 메모리를 해제하는 작업은 ns-3에도 Ptr<T>로 구현되어 있는데, std::shared_ptr, boost::shared_ptr은 아직 써본 적이 없어서 자세히 모르겠지만 아마 개념과 목적은 유사할 것으로 예상된다.

(나중에 사용한 뒤에 포스트를 수정할 예정)




반응형
블로그 이미지

Bryan_

,
반응형

나는 여러 개의 특정한 데이터 여러 개를 묶어서 관리해야 할 때 습관적으로 vector를 많이 쓴다.


일단은 코딩하면서 생각 없이 vector<T>로 특정 타입의 데이터를 그룹화시키고, 점차 요구사항이 늘어남에 따라 해당 vector에 접근해서 특정 항목만추려내는 것 같은 새로운 기능을 함수로 만들어서 추가하곤 한다.


그렇게 코드를 확장해 가다가, 문득 특정 항목들을 모아 두는 vector에 값이 중복되어 들어가면 안 되는 요구사항을 발견했다. 원래부터 그런 요구사항이 있었는데, 미처 생각하지 못하고 일단 무작정 vector로 만들었다.

그래서 별 생각 없이 vector에 항목을 추가하는 함수에서 먼저 vector를 traverse하면서 새로 추가하려는 항목이 이미 존재하는지 확인하는 코드를 추가하려고 했다. loop를 만들다 보니, 갑자기 중복된 값을 허용하지 않는 데이터 구조인 set을 쓰면 굳이 이렇게 코딩할 필요가 없다는 것을 깨닫게 되었다. 즉, 데이터 구조로 set을 쓰고, 추가할 때 중복된 값의 존재 유무를 확인하는 코드를 추가할 필요도 없이 그냥 기존에 하던 대로 고민없이 추가하면 되는 것이었다.


내가 set 개념은 알고 있었지만, 평소에 잘 쓰지 않다 보니 이런 일이 생긴 것 같다. 고민 없이 너무 습관적으로 vector로 일단 만들고 보니 vector가 처리해 주지 못하는 이슈를 처리하기 위해서 나중에 코드를 추가로 보완해야 하는 상황이 된 것이다.


결국은 많이 써 보고, 조금만 더 데이터 구조의 적합성에 대한 고민을 해 본 다음에 코딩을 진행할 필요가 있다. 근본적으로 보자면, 요구사항을 도출하고 분석하는 단계에서 vector가 적합한지 미리 생각함으로써 설계 단계에서는 이미 적합한 데이터 구조가 결정되는 것이 이상적이다.


물론 내 경우에는 네트워크에서 라우팅 과정을 시뮬레이션으로 만들고, 그 과정에서 빈번하게 요구사항이 변화하기 때문에 어쩔 수는 없다. 하지만 적어도 어느 정도 선에서 완성해야 할 모양에 대한 그림이 나오면 그걸 완성하기 위한 요구사항 분석과 설계는 신경써서 함으로써, 나중에 안해도 될 코딩을 더 하거나 비효율적인 코드가 더해지는 상황은 예방하는 습관을 들여야 하겠다.




반응형
블로그 이미지

Bryan_

,
반응형

Java에서 사용자가 정의한 클래스를 java.util.HashMap에서 key 값으로 사용하도록 만들 때, Overriding 메쏘드 중에서 hashCode 메쏘드를 제대로 구현해야 문제없이 작동한다. 사용자 정의 클래스의 몇몇 멤버변수 값들이 다른데도 불구하고 hashCode를 제대로 구현하지 않으면 차이가 없는 것으로 판단할 수도 있기 때문이다.


C++에서는 map<const Key, T>이 비슷한 역할을 하는데, Java처럼 hashCode 함수 대신 operator < 함수를 써서 키값을 비교하는 것으로 생각된다. 

처음에는 operator = 함수만 신경써서 만들고, 실수로 operator < 함수의 내부 구현을 대충 했더니, map의 key로 서로 다른 멤버변수 값을 갖는 클래스를 대입했는데 모두 같은 value를 리턴하는 것이었다.


내 경우에는 네트워크 상의 트래픽을 표현하기 위해 Flow라는 클래스를 아래와 같이 정의했다.


class Flow {

public:

std::string srcIp;

int srcPort;

std::string dstIp;

int dstPort;

int type;

Flow();

~Flow();

};


그리고 실수로 "operator <" 함수를 아래와 같이 간단하게 만들었더니,


bool Flow::operator <(const Flow& ref) const {

return (this->srcPort + this->dstPort) < (ref.getSrcPort() + ref.getDstPort()));

}


source 또는 destination IP 주소가 서로 다른 여러 개의 flow가 모두 같은 key 값을 갖게 되는 문제가 발생했다.


그래서 std::string 형식의 IP 주소에서 숫자를 빼내서 이것들도 모두 더하는 방식으로 operator <를 구현해서 key 값이 서로 달라지도록 만들었다.


bool Flow::operator <(const Flow& ref) const {

return ((convert(this->srcIp) + this->srcPort + convert(this->dstIp) + this->dstPort) < (convert(ref.getSrcIp()) + ref.getSrcPort() + convert(ref.getDstIp()) + ref.getDstPort()));

}


이런 부분에서의 실수는 컴파일 에러에서 전혀 잡히지 않고, 가끔은 Segmentation fault 같은 에러조차 발생시키지 않고서 정상적이면서 의미상으로만 이상하게 작동하기 때문에 앞으로는 더 주의해야 할 것이다.



반응형
블로그 이미지

Bryan_

,
반응형

OS: Ubuntu 14.04 (amd64)

g++ version: 4.8.4

Eclipse: Mars, Neon



이클립스(Eclipse)에서 C++2011  문법(std::thread나 향상된 for loop 같은 것)을 쓰려고 하면 빌드 설정에 -std=c++0x 또는 -std=c++11 같은 플래그를 추가해 두어도 정작 에디터에서 계속 syntax error를 표시하는 경우가 있다.


(이클립스 에디터가 C++11 문법에 따라 쓴 std::thread 코드를 

unresolved symbol error로 표시하고 있다.)



해결하려면, 프로젝트 이름에 마우스 오른쪽 단추를 누르고 Properties로 가서,

C/C++ General > Preprocessor Include Paths, Macros etc.로 간 다음,

Providers 탭에서 CDT GCC Build-in Compiler Settings를 선택한다.


그리고 하단에 "Use global provider shared between projects" 선택을 해제하고,

Command to get compiler specs 칸에 -std=c++0x 를 추가한다.


또한 순서상 중간 쯤에 와 있는 CDT GCC Build-in Compiler Settings 항목을 Move Up을 이용해서 가장 위로 끌어올린 뒤에 Apply 버튼과 OK 버튼을 누른다.




마지막으로, 이클립스 메뉴에서 

Project > C/C++ Index > Re-resolve Unresolved Includes

Project > C/C++ Index > Freshen All Files

Project > C/C++ Index > Rebuild

를 차례대로 눌러 주면 에러로 표시하지 않게 된다.


(C++ 2011 문법의 syntax error가 해결된 이후의 에디터 화면)




<참고자료>

[1] http://stackoverflow.com/questions/17457069/enabling-c11-in-eclipse-juno-kepler-luna-cdt



반응형
블로그 이미지

Bryan_

,
반응형

OS: Ubuntu 14.04 (amd64)

g++ version: 4.8.4



C++11에 정의되어 있는 thread를 코드에 사용해서 이클립스에서든 Makefile을 직접 써서든 그냥 빌드하면 컴파일 에러가 나는데, 이 때는 -std=c++11 플래그를 추가하라고 나와 있다. [5]

다만 쓰려는 코드가 또 쓰레드이기 때문에 -pthread도 추가하도록 되어 있다.


하지만 위와 같은 플래그를 추가하고 나서도 실행 단계에서 아래와 같은 에러가 나면서 실행이 중단된다.


terminate called after throwing an instance of 'std::system_error'

  what(): Enable multithreading to use std::thread: Operation not permitted


StackOverflow [1, 2]를 보면 gcc의 버그라는 게 결론이고, gcc 패키지에도 버그로 보고되어 있다 [3]. 


해결책으로 특정 flag를 추가해 줘야 한다고 나와 있다.

기본적으로 -Wl,--no-as-needed 를 추가해 주면 된다고 한다.


그런데 내 경우에는 g++ 4.8.4를 쓰고 있어서 flag 하나를 더 추가해 줘야 한다는 댓글이 있어서 최종적으로 -std=c++11 -pthread -lpthread -Wl,--no-as-needed 이렇게 추가를 했더니 빌드와 실행 모두 잘 되었다.


Eclipse CDT에서 설정하는 방법은 [4]에 설명되어 있다. Project Properties에서 C/C++ Build 부분은 빌드할 때 옵션이고, C/C++ General 쪽에 해주는 설정은 아마 에디터에서 보여지는 문법상의 에러를 표시할 때 필요한 것 같은데, 이상하게 에디터 쪽에서는 잘 적용이 되지 않는 것 같았다.




<참고자료>

[1] http://stackoverflow.com/questions/19463602/compiling-multithread-code-with-g

[2] http://chat.stackoverflow.com/transcript/message/12447014#12447014

[3] https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1228201

[4] http://wiki.eclipse.org/CDT/User/FAQ#CDT_does_not_recognize_C.2B.2B11_features

[5] http://stackoverflow.com/questions/9131763/eclipse-cdt-c11-c0x-support



반응형
블로그 이미지

Bryan_

,