Ilaria Ilaria - 25 days ago 22
C++ Question

Armadillo linking error only if I use it in my own library

I'm having some troubles with Armadillo when I use it within my own library. I installed Armadillo, and I created my own library, which calls the svd_econ method. This is the CMakeList.txt file:

cmake_minimum_required (VERSION 2.8.9)
set(PROJECTNAME training_library)
project(${PROJECTNAME})

if (NOT DEFINED ENV{EIGEN_ROOT})
message(FATAL_ERROR "Could not find EIGEN_ROOT environment variable")
endif()

find_package(Armadillo REQUIRED)

include_directories("$ENV{EIGEN_ROOT}")

add_definitions(-g)
add_definitions(-O3)
add_definitions(-DNDEBUG)

include_directories(${ARMADILLO_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/include)

file(GLOB header include/*.h)
file(GLOB source src/*.cpp)

source_group("Source Files" FILES ${source})
source_group("Header Files" FILES ${header})

add_library(${PROJECTNAME} ${source} ${header})
target_link_libraries(${PROJECTNAME} ${ARMADILLO_LIBRARIES})


This is the header file:

#include <algorithm>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <armadillo>
#include <Eigen/Dense>
#include <Eigen/SVD>

namespace statistics
{
bool findPCS(const Eigen::MatrixXd &data, Eigen::VectorXd &eigenvalues, Eigen::MatrixXd &eigenvectors);
}

#endif


and this is the .cpp:

#include "statistics.h"

namespace statistics
{

Eigen::MatrixXd convert(const arma::mat &data)
{
Eigen::MatrixXd res(data.n_rows,data.n_cols);
for (int i = 0; i < data.n_rows; i++)
{
for (int j = 0; j < data.n_cols; j++)
{
res(i,j) = data(i,j);
}
}
return res;
}

arma::mat convert(const Eigen::MatrixXd &data)
{
arma::mat res(data.rows(),data.cols());
for (int i = 0; i < data.rows(); i++)
{
for (int j = 0; j < data.cols(); j++)
{
res(i,j) = data(i,j);
}
}
return res;
}

Eigen::VectorXd convert(const arma::vec &data)
{
Eigen::VectorXd res(data.size());
for (int i = 0; i < data.size(); i++)
{
res(i) = data(i);
}
return res;
}

arma::vec convert(const Eigen::VectorXd &data)
{
arma::vec res(data.size());
for (int i = 0; i < data.size(); i++)
{
res(i) = data(i);
}
return res;
}

bool findPCS(const Eigen::MatrixXd &data, Eigen::VectorXd &eigenvalues, Eigen::MatrixXd &eigenvectors)
{
arma::mat arma_mat = convert(data);
arma::mat U; arma::vec S; arma::mat V;
arma::svd_econ(U,S,V,arma_mat);
eigenvalues = convert(S)/sqrt((double)(data.rows()));
eigenvectors = convert(V);

return true;
}

}


Now, this library compiles without any problems. Then, I want to call the findPCS method from another different module called clean_data. The CMakeLists.txt file for it is the following:

cmake_minimum_required (VERSION 2.8.9)
set(PROJECTNAME clean_data)
project(${PROJECTNAME})

find_package(OpenCV REQUIRED)
find_package(Armadillo REQUIRED)

if (NOT DEFINED ENV{EIGEN_ROOT})
message(FATAL_ERROR "Could not find EIGEN_ROOT environment variable")
endif()

include_directories("$ENV{EIGEN_ROOT}")
include_directories("$ENV{training_INCLUDE}")
link_directories("$ENV{training_LIBS}")

add_definitions(-g)
add_definitions(-O3)
add_definitions(-DNDEBUG)

file(GLOB SOURCES *.c *.cpp)

add_executable(${PROJECTNAME} ${SOURCES})
target_link_libraries(${PROJECTNAME} ${OpenCV_LIBS} ${ARMADILLO_LIBRARIES} training_library)


The main.cpp which calls the findPCS method is as follows:

#include <armadillo>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <numeric>
#include <list>
#include <statistics.h>
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>

int main() {

Eigen::MatrixXd matrix(4,10);
matrix << 1, 2, 2, 1, 5, 5, 4, 12, 32, 1,
44, 34, 12, 11, 21, 5, 54, 1, 12, 3,
33, 128, 112, 126, 3, 3, 2, 12, 1, 54,
66, 34, 1, 54, 2, 2, 1, 43, 4, 12;
Eigen::VectorXd eigenval; Eigen::MatrixXd eigenvec;
statistics::findPCS(matrix,eigenval,eigenvec);

//arma::mat x = arma::randu<arma::mat>(3,4);
//arma::mat U; arma::vec S; arma::mat V;
//arma::svd(U,S,V,x);

return 0;
}


This code, when I try to compile, gives me this linking error:

/home/ilaria/Dev/training_library/build/libtraining_library.a(statistics.cpp.o): In function `gesdd<double>':
/usr/local/include/armadillo_bits/wrapper_lapack.hpp:571: undefined reference to `wrapper_dgesdd_'
/home/ilaria/Dev/training_library/build/libtraining_library.a(statistics.cpp.o): In function `gesvd<double>':
/usr/local/include/armadillo_bits/wrapper_lapack.hpp:506: undefined reference to `wrapper_dgesvd_'
/usr/local/include/armadillo_bits/wrapper_lapack.hpp:506: undefined reference to `wrapper_dgesvd_'
collect2: error: ld returned 1 exit status
make[2]: *** [clean_data] Error 1
make[1]: *** [CMakeFiles/clean_data.dir/all] Error 2
make: *** [all] Error 2


However, if I decomment the lines that are commented in my main.cpp, the code compiles without any problems, it runs fine and it gives me the right results! I looked on the Internet, and I couldn't find any useful answer. I'm using Ubuntu 14.04. I installed armadillo from sources, and lapack, blas and openblas from synaptic. Can anyone spot a problem anywhere? Any help would be much appreciated.

Thanks,
Ilaria

Answer

Your problem boils down to library linking order. I don't know your full linking line, but here is a simplification to illustrate the problem:

gcc -o myOutput main.o -l someLibrary -l training_library

Now training_library.a contains references to external symbols: in particular wrapper_dgesvd_ amongst others. These are in fact defined in someLibrary.a (I don't know the actual library name here, so use this moniker to illustrate my point). However, due to the -l order above they cannot be resolved (the rule is that unresolved symbols on the linker line get processed left-to-right, and only the -l entries appearing to the right of the point where an unresolved symbol was introduced can resolve it).

So why does uncommenting the lines in main.cpp help? Because making the calls in the commented lines presumably (under the hood) references those same unresolved symbols (so wrapper_dgesvd_ for example). Because these are now introduced to the left of -l someLibrary, this can now be used to resolve them and so they are pulled in. Once pulled in they are now also available for things appearing later on in the command line, i.e. for the training_library, so your earlier problem goes away.

How to solve this? Just a guess, but try changing your CMake line from:

target_link_libraries(${PROJECTNAME} ${OpenCV_LIBS} ${ARMADILLO_LIBRARIES} training_library)

To:

target_link_libraries(${PROJECTNAME} training_library ${OpenCV_LIBS} ${ARMADILLO_LIBRARIES})
Comments