[ROS] multiple definition of ... 오류
=======================================
‘22년 08월 16일 업데이트
내용이 두서 없어서 하기의 링크로 다시 정리했습니다.
좀 더 읽기 편하니 참고 부탁 드립니다.
=======================================
문제상황
- 변수나 함수, 쓰레드 등을 헤더에 선언하였을 때, 다음과 같은 오류 메세지를 출력함.
- class 나 struct 내에 변수와 함수를 선언하면 오류가 생기지 않음.
원인
- 특정 헤더파일(.hpp 또는 .h) 가 서로 다른 소스 (.cpp 또는 .c)에서 여러번 include 되었을 때 나타나는 에러이다.
- 여러 번 include 되면서 multiple definition을 정의하게 된다.
- 함수나 변수를 헤더파일에 선언하는 것은 "header가 include 되는 각각의 translation(by compiler) 마다 각각의 definition이 정의되는 것"과 같다.
- 하나의 ROS 패키지에서는 한번의 definition만이 허용된다 : 정의는 반드시 한번만 이뤄져야 한다. (One Definition Rule)
해결방법
- 크게 세 가지의 해결법이 있다.
(1) CMakeLists.txt 수정
(2) 선언된 변수 및 함수의 선언 방식 수정 (묵시적 inline 화)
(3) inline 함수로 선언하여 해결한다.
(1) CMakeLists.txt 수정
- 여러번 include 되는 헤더를 하나의 library로 CMakeLists.txt에 명시하여 multi-define 되지 않도록한다.
- 단, 상호 간 dependency 를 주의해야한다. circle이 되지 않도록 항상 상위관계를 명확하게 선언해야한다.
Leishen 사의 lslidar c16 코드를 예시로 들어보자.
해당 소스의 주요 패키지는 크게 decoder, driver로 나누어 생각해볼 수 있다.
이중 decoder 패키지의 소스는
- lslidar_c16_decoder.cpp
- lslidar_c16_decoder_node.cpp
- lslidar_c16_decoder_nodelet.cpp
으로 나눌 수 있다.
해당 패키지의 헤더 중 `lslidar_c16_decoder.h`는 src/lslidar_c16_decoder_node.cpp와 src/lslidar_c16_decoder.cpp 모두의 선언부에
#include <lslidar_c16_decoder/lslidar_c16_decoder.h>
로 include 되는 것을 확인할 수 있다.
그러나, `lslidar_c16_decoder.h`내의 namespace lslidar_c16_decoder에서 class 또는 struct 밖에서 변수와 함수를 선언해도 문제가 되지 않는다.
해당 이유는 lslidar_c16_decoder 패키지의 CMakeLists.txt에서 확인할 수 있다.
...중략...
# Lslidar C16 Decoderadd_library(lslidar_c16_decodersrc/lslidar_c16_decoder.cpp)target_link_libraries(lslidar_c16_decoder${catkin_LIBRARIES})add_dependencies(lslidar_c16_decoder${${PROJECT_NAME}_EXPORTED_TARGETS}${catkin_EXPORTED_TARGETS})# Lslidar C16 Decoder nodeadd_executable(lslidar_c16_decoder_nodesrc/lslidar_c16_decoder_node.cpp)target_link_libraries(lslidar_c16_decoder_nodelslidar_c16_decoder ${catkin_LIBRARIES})add_dependencies(lslidar_c16_decoder_node${${PROJECT_NAME}_EXPORTED_TARGETS}${catkin_EXPORTED_TARGETS})# Lslidar C16 Decoder nodeletadd_library(lslidar_c16_decoder_nodeletsrc/lslidar_c16_decoder_nodelet.cpp)target_link_libraries(lslidar_c16_decoder_nodeletlslidar_c16_decoder${catkin_LIBRARIES})add_dependencies(lslidar_c16_decoder_nodelet${${PROJECT_NAME}_EXPORTED_TARGETS}${catkin_EXPORTED_TARGETS})... 중략 ...
위의 ./lslidar_c16_decoder/CMakeLits.txt 는 각각의 cpp 파일에 대한 라이브러리와 dependency를 각각 명시하고 있다.
add_library는
add_library (<사용자가_지정한_library_이름>
소스1_경로/소스1.cpp
소스2_경로/소스2.cpp
..중략..
)
로 사용할 수 있기 때문에, 위의
# Lslidar C16 Decoderadd_library(lslidar_c16_decodersrc/lslidar_c16_decoder.cpp)
는 'lslidar_c16_decoder' 라는 라이브러리명을 지정하고 해당 라이브러리는src/lslidar_c16_decoder.cpp 내에 선언된 정의를 지칭하는 것이다.
따라서,
# Lslidar C16 Decoder nodeadd_executable(lslidar_c16_decoder_nodesrc/lslidar_c16_decoder_node.cpp)target_link_libraries(lslidar_c16_decoder_nodelslidar_c16_decoder${catkin_LIBRARIES})add_dependencies(lslidar_c16_decoder_node${${PROJECT_NAME}_EXPORTED_TARGETS}${catkin_EXPORTED_TARGETS})# Lslidar C16 Decoder nodeletadd_library(lslidar_c16_decoder_nodeletsrc/lslidar_c16_decoder_nodelet.cpp)target_link_libraries(lslidar_c16_decoder_nodeletlslidar_c16_decoder${catkin_LIBRARIES})
해당 부분은 decoder_node와 decoder_nodelet이 모두 src/ lslidar_ c16_ decoder.cpp 를 `target_link_libraries`를 통해 내부 라이브러리를 동일하게 링크하는 것으로 명시하여 각각의 .cpp 파일에서
#include <lslidar_c16_decoder/lslidar_c16_decoder.h>
를 중복으로 include하여도 multiple definition 오류가 나지 않는 것이다.
(2) 오류가 발생하는 변수나 함수를 struct 또는 class 내에 선언한다.
- 오류가 발생한 변수 또는 함수의 선언을 struct와 class 내부로 옮긴다.
- struct, class 내에 선언되는 함수는 자동적으로 inline화 된다.
- inline화 되면 그 자체가 내용이 되므로, 호출을 통해 생기는 multiple definition 에러를 잡아줄 수 있다.
- Declaring the variables and functions in the ‘struct’ or ‘class’ makes them appear in only one translation unit.
<오류 메세지>
CMakeFiles/lidar_vision_node.dir/src/lidar_vision.cpp.o:(.bss+0x0): multiple definition of `lidar_vision::topic_list'CMakeFiles/lidar_vision_node.dir/nodes/lidar_vision_node.cpp.o:(.bss+0x0): first defined herecollect2: error: ld returned 1 exit status
- main 함수가 있는 ./lidar_vision_node.cpp 가 다음과 같을 때,
#include "lidar_vision_manager.h"
int main (int argc, char** argv){ ros::init(argc, argv, "lidar_vision_pkg"); lidar_vision::Lidar_Vision_Node lidarVisionNode; lidarVisionNode.RunVision(); return EXIT_SUCCESS;}
./include/lidar_vision_manager.h 는 다음과 같이 작성되는 것이 바람직함.
#include "ros/ros.h"#include "topic_list.h"namespace lidar_vision{ class Lidar_Vision_Node { private: //Node handle for ROS ros::NodeHandle nh_;
//create ROS I/O bool createRosIO();
TOPIC_LIST topic_list; ros::Publisher lidar_vision_pub; ros::Subscriber point_cloud_sub;
public: Lidar_Vision_Node(); ~Lidar_Vision_Node(); void RunVision(void); };
} //end namespace lidar_vision
즉, 모든 함수와 변수를 class 내에 선언한다.
(3) inline 함수로 선언하여 해결한다.
- ‘multiple definition of…’ 오류가 나는 함수나 변수를 직접 inline 선언한다.
- 하지만 (2)의 방법이 좀 더 일반적이므로 (2)를 최종적으로 추천.
참조 :
https://stackoverflow.com/questions/9959563/why-do-i-get-a-multiple-definition-error-while-linking