2

I am new to C++. I compiled some code from boost/asio examples using g++.

I was successful only after using -std=c++14 at the g++ command line. I understood then, and I may be wrong, that the boost/asio library uses C++14.

Then I tried to add code for encoding and decoding base64.

And I received a linker error:

undefined reference to base64_decode(std::__cxx11:basic.....

I understood by the error that I am passing a std:string in C++11 format and the linker does not find a reference in the files of a function that accepts an argument in that format.

But, where in the code can I see which format this method accepts? If my code boost/asio have a std::string and I passed it to this function:

std::string base64_decode(std::string const& s)

Why does the linker inform me that this parameter is a C++11 std::string?

How does this work?

Where, in the code, is the version of C++ being used?

This is the Base64 file I tried to add:

#ifndef _BASE64_H_
#define _BASE64_H_
#include <string>
std::string base64_encode(unsigned char const* , unsigned int len);
std::string base64_decode(std::string const& s);
#endif  

And this the cpp file

#include "base64.h"

static const std::string base64_chars = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";


static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3))
      ret += '=';

  }

  return ret;

}

std::string base64_decode(std::string const& encoded_string) {
  size_t in_len = encoded_string.size();
  size_t i = 0;
  size_t j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;

  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = static_cast<unsigned char>(base64_chars.find(char_array_4[i]));

      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }

  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;

    for (j = 0; j <4; j++)
      char_array_4[j] = static_cast<unsigned char>(base64_chars.find(char_array_4[j]));

    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }

  return ret;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Neumann
  • 319
  • 2
  • 14
  • 1
    Header guards usually go in the `.h` file and not the `.cpp` file. As for what version of C++ is being used there is no way to explicitly see from the code what version of C++ is in use. – cogle Nov 16 '17 at 19:51
  • 1
    @cogle: "*there is no way to explicitly see from the code what version of C++ is in use*" - but you can test for it in code using the `__cplusplus` precompiler symbol (though some compilers lie about its value) – Remy Lebeau Nov 16 '17 at 20:08
  • My guess about the error is that you are *compiling* for C++14 but you are *linking* to the libs of a different version, that is why `std::string` can't be resolved correctly. Make sure compiler and linker match up properly. Your *code* is fine to use `std::string`, the mapping of `std` to `std::__cxx11` is a *private implementation detail* that you shouldn't concern yourself with. – Remy Lebeau Nov 16 '17 at 20:11
  • @RemyLebeau: Interesting, can you elaborate on where you've seen compilers lying about the value? – AndyG Nov 16 '17 at 20:58
  • 1
    @AndyG: search around, it is a well-known fact. `__cplusplus` is suppose to contain a specific number of the C++ version being used, but some compilers don't always use correct numbers. For example, if compiling for C++11, but not all C++11 features are implemented, a compiler might report an earlier number. Some compilers don't always use the *same* value. For instance, GCC 6.1 and 7.0 used 201500 for C++14, but Clang 3.8 and 3.9 used 201406 instead (the correct value is 201402). And sometimes compiler vendors just forget to increment it. So best not to rely on the *value* of `__cplusplus`. – Remy Lebeau Nov 16 '17 at 22:18

2 Answers2

2

The issue you are experiencing is caused by the fact that C++11 changed requirements to std::string (or more preceisly - std::basic_string) disallowing usage of copy-on-write semantics used by libstdc++ previously.

To adopt C++11, libstdc++ had to change layout of std::basic_string, breaking existing applications. To avoid this, C++11 strings use different name internally. In <bits/basic_string.h> you can find this macro:

# define _GLIBCXX_BEGIN_NAMESPACE_CXX11 namespace __cxx11 {

which is used in <string>. So the actual name of C++ std::string in libstdc++ starts with std::__cxx11::, not with std::.

Since libstdc++ provides both C++11 and C++98 versions of std::string, you need to make sure your code is consistent in its usage of this class - all code and libraries should use only one version to be interoperable e.g. everything should be build with either C++98/C++03 or with C++11 and newer. In short, you have to use -std=c++11, -std=c++14, -std=c++17 flags of compiler consistently.

  • Thank you so much for your help. Now I tried to define _GLIBCXX_USE_CXX11_ABI to 0 and received this return from g++: MACRO redefined, this is the location of the previous definition C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/x‌​86_64-w64-mingw32/bi‌​ts/c++config.h:212:0‌​: # define _GLIBCXX_USE_CXX11_ABI 1. Ok, is on. How can I change this class base64.h to dont return undefined reference ??? – Neumann Nov 17 '17 at 12:31
  • You don't need to touch those macros, you should make sure all your code is build using proper C++ standard switch. If you use libraries build for C++03, you should too build with `-std=c++03` switch. If your libraries use C++11, you should use at least `-std=c++11` switch. For starters, try building with both. You don't need to touch your code. –  Nov 17 '17 at 12:34
  • As I said, my project did build and link without fail. THEN I tried to include this base64 files, only one header and your cpp file, not a lib. And nothing more works. How can I change this files? What in this files cause this ABI undefined reference? – Neumann Nov 17 '17 at 12:53
  • Sorry Ivan, I was reading again and I understood that I need to compile files with diferent directives and link objects after. Is this? – Neumann Nov 17 '17 at 13:00
  • @Neumann Yes. I can't tell you why your project build before, probably it didn't use `std::string`. –  Nov 17 '17 at 13:14
  • Thank you so much @Ivan. I compiled only base64.cpp with c++11 directive. And then added the base64.o file int the gcc line command of my project with c++14 directive and linked without restrictions. Thanks. – Neumann Nov 17 '17 at 16:47
1

To answer the question you asked (but perhaps not the one you intended) ("What in code defines c++ version? ")

Implementations are supposed to define the __cpluplus macro ([cpp.predefined]) which resolves to the following values.

  • C++03: 199711L
  • C++11: 201103L
  • C++14: 201402L
  • C++17: 201703L

With each new standard, this value is intended to become larger.


That said, when you begin linking, different libraries may or may not be compatible (ABI compatible), even for compiler versions that define the same value for __cplusplus. If you build a C++11 project, you should prefer to link against libraries also compiled with the same compiler for the same platform.

It's more of a case-by-case thing, though. You should consult the documentation for the given library version that you use. Boost in particular talks about compiler compatibility in its release notes. Boost's Windows libraries also list the compiler version (e.g. Boost 1.63 for visual studio 2015 all have vc140 in their name).


Since your error message appears to pertain to std::__cxx11, we can deduce you're using gcc. They have written this helpful page about your exact issue. Here's the most important line:

If you get linker errors about undefined references to symbols that involve types in the std::__cxx11 namespace [...] then it probably indicates that you are trying to link together object files that were compiled with different values for the _GLIBCXX_USE_CXX11_ABI macro. This commonly happens when linking to a third-party library that was compiled with an older version of GCC

And a little more about the _GLIBCXX_USE_CXX11_ABI macro:

In the GCC 5.1 release libstdc++ introduced a new library ABI that includes new implementations of std::string and std::list. These changes were necessary to conform to the 2011 C++ standard which forbids Copy-On-Write strings and requires lists to keep track of their size...

The _GLIBCXX_USE_CXX11_ABI macro (see Macros) controls whether the declarations in the library headers use the old or new ABI. So the decision of which ABI to use can be made separately for each source file being compiled. Using the default configuration options for GCC the default value of the macro is 1 which causes the new ABI to be active, so to use the old ABI you must explicitly define the macro to 0 before including any library headers.

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Thank you so much for your help. Now I tried to define _GLIBCXX_USE_CXX11_ABI to 0 and received this return from g++: MACRO redefined, this is the location of the previous definition C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/x86_64-w64-mingw32/bits/c++config.h:212:0: # define _GLIBCXX_USE_CXX11_ABI 1. – Neumann Nov 17 '17 at 12:09
  • @Neumann: It's set to `1` by default. You are overriding it to `0` so that is an expected message. Does the code link after you've set it to 0? If not, then best to just compile everything with the same gcc version. – AndyG Nov 17 '17 at 14:26