Неожиданное поведение на этапе компоновки при использовании #ifdef - PullRequest
0 голосов
/ 20 марта 2020

Я пытаюсь протестировать класс, содержащий член сторонней библиотеки.

Я создал поддельную библиотеку для mimi c вышеупомянутого и использую директивы препроцессора для выбора между "реальным" и поддельные библиотеки во время компиляции - реальные для модуля производственной компиляции и поддельные для теста.

Проблема в том, что я вижу некоторые расхождения между тем, что выходит из препроцессора, и тем, что создает компоновщик.

Вот чрезвычайно урезанная версия установки, которую я имею:

Сторонняя библиотека - lib.hpp

Это библиотека только для заголовков, поэтому все функции встроены.

namespace lib {

class Foo {
public:
  int returnSomething() const { return 42; }
};

}

Поддельная библиотека

В действительности она довольно короткая, поэтому я разбил ее на заголовочные и исходные файлы:

Header - fake_lib.hpp

namespace fake {

class FooBar {
public:
  int returnSomething() const;
};

}

Источник - fake_lib. cpp

#include "fake_lib.hpp"

namespace fake {

int FooBar::returnSomething() const {
  return 13;
}

}

Тестируемый класс - bar.hpp и bar. cpp

#ifdef SWITCH
#include "fake_lib.hpp"
#endif

// There is no #else here for the preprocessor because in my actual use case we
// need some times from the third_party libs whether we test or are in production
#include "lib.hpp"

class Bar {
public:
  int get();

private:
#ifndef SWITCH
#warning "real"
  lib::Foo m_foo;
#else
#warning "fake"
  fake::FooBar m_foo;
#endif

};
#include "bar.hpp"

int Bar::get() {
  return m_foo.returnSomething();
}

Exe

#include "bar.hpp"

#include <iostream>

int main() {
  Bar bar;

  std::cout << bar.get() << std::endl;

  return 0;
}

Испытательный exe

#include "bar.hpp"

#include <iostream>

int main() {
  Bar bar;

  std::cout << bar.get() << std::endl;

  return 0;
}

Это все построено через CMake, вот мой CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)

project(fake_failure LANGUAGES CXX)

add_library(lib INTERFACE)
target_include_directories(lib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/lib)

add_library(fake OBJECT fake/fake_lib.cpp)
target_include_directories(fake PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/fake)

add_library(bar OBJECT bar.cpp)
target_include_directories(bar PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(bar PUBLIC lib) 

add_executable(main_exe main.cpp)
target_link_libraries(main_exe PRIVATE bar)

add_executable(fake_exe fake.cpp)
target_compile_definitions(fake_exe PRIVATE SWITCH)
target_link_libraries(fake_exe PRIVATE bar fake)

Желаемый результат

Когда main_exe запущен, он должен вывести 42. Когда fake_exe запущен, он должен напечатать 13.

Фактический результат

fake_exe также распечатывает 42.

Теперь, просто чтобы убрать это с дороги - я могу это исправить и получить желаемое на данный момент, определив библиотеку stati c fake_bar, которая добавляет макрос SWITCH.

Что я хотел бы узнать

Когда я запускаю препроцессор вручную, я вижу этот член Bar m_foo, если он поддельного типа. Я предполагаю, что поскольку библиотека bar компилируется без макроса SWITCH, она использует встроенные определения, что означает, что созданный объектный файл имеет встроенные функции в виде символов. Почему компоновщик не выдает ошибку, если он не видит символы для фальшивого типа?

И почему при запуске gdb происходит взлом Bar и выполнение print typeid(m_foo) Я вижу это реального типа, даже если вывод препроцессора, о котором я упоминал выше, говорил иначе.

Вышеприведенное объяснило бы, почему компоновщик не выдает ошибок - он даже не пытается использовать поддельные символы. Но я не могу понять, почему.

Заранее спасибо!

1 Ответ

0 голосов
/ 20 марта 2020

Вы нарушаете правило ODR . Определение типа класса Bar в объектном файле, сгенерированном из fake.cpp и bar.cpp, состоит из различной последовательности токенов - в частности, член Bar::m_foo имеет разный тип в обеих единицах перевода.

Поведение вашей программы fake_exe равно undefined .

Я предполагаю, что поскольку библиотека столбцов компилируется без макроса SWITCH, она использует встроенные определения, то есть созданный объектный файл имеет встроенные функции в виде символов.

Нет, это не значит, что. Ключевое слово inline only поощряет компилятор встроить функцию. Компилятору разрешено не включать функцию. cppreference inline

В любом случае, Bar::get(), скорее всего, не встроен вашим компилятором, потому что он находится в другом модуле перевода (но может быть встроен в любом случае). Поскольку Bar::get() «видит» lib::Foo m_foo;, это означает, что компилятор сгенерирует вызов для lib::Foo::returnSomething() в Bar::get(), поэтому Bar::get() вернет 42. Проверьте сгенерированный ассемблерный код, чтобы действительно знать, что компилятор делает с вашим кодом.

Почему компоновщик не выдаст ошибку, если он не может видеть символы для поддельного типа?
почему компоновщик не выдает ошибок

, поскольку компоновщик не располагает информацией для диагностики и выдачи таких ошибок. Линкер не имеет информации о типе символов. Линкер видит почти только имена символов.

...