Я проверяю, как программа Windows link.exe реагирует на нарушения ODR. Я хочу, чтобы компоновщик потерпел неудачу, когда заметит, что в моей программе есть два определения одной и той же функции (dynamic_func
), одно из которых взято из DLL. У меня есть следующее:
/* main.cpp */
#include "dynamic_lib.h"
#include "static_lib.h"
int main()
{
dynamic_func();
static_func();
return 0;
}
/* dynamic_lib.h */
#ifndef DYNAMIC_LIB_H
#define DYNAMIC_LIB_H
extern "C" {
#ifdef HEADER_ONLY
# define INLINE inline
# define DYNAMIC_LIB_API
#else
# define INLINE
# ifdef _WIN32
# ifdef BUILDING_LIB
# define DYNAMIC_LIB_API __declspec(dllexport)
# else
# define DYNAMIC_LIB_API __declspec(dllimport)
# endif /* BUILDING_LIB */
# else
# define DYNAMIC_LIB_API
# endif /* _WIN32 */
#endif /* HEADER_ONLY */
DYNAMIC_LIB_API void dynamic_func();
}
#ifdef HEADER_ONLY
# include "dynamic_lib.cpp"
#endif
#endif /* DYNAMIC_LIB_H */
/* dynamic_lib.cpp */
#include "dynamic_lib.h"
INLINE void dynamic_func()
{}
/* static_lib.h */
#ifndef STATIC_LIB_H
#define STATIC_LIB_H
extern "C" {
void static_func();
}
#endif /* STATIC_LIB_H */
/* static_lib.cpp */
#include "static_lib.h"
#include "dynamic_lib.h"
void static_func()
{
dynamic_func();
}
/* CMakeLists.txt */
cmake_minimum_required(VERSION 3.20.0 FATAL_ERROR)
project(Test CXX)
set(CMAKE_CXX_STANDARD 20)
add_library(dynamic_lib SHARED
dynamic_lib.cpp
)
target_compile_definitions(dynamic_lib PRIVATE BUILDING_LIB)
add_library(static_lib STATIC
static_lib.cpp
)
target_compile_definitions(static_lib PRIVATE HEADER_ONLY)
add_executable(main
main.cpp
)
target_link_libraries(main PUBLIC
dynamic_lib
static_lib
)
При попытке связать main.exe это корректно завершается с ошибкой переопределения символа LNK2005 для dynamic_func
.
Однако если я изменю пример так, чтобы static_lib.cpp определял dynamic_func
, этап связывания будет работать нормально:
/* static_lib.cpp */
#include "static_lib.h"
void dynamic_func()
{}
void static_func()
{
dynamic_func();
}
В обоих случаях вызовы компилятора и компоновщика идентичны. Кроме того, первый пример, похоже, отлично работает как с gcc, так и с clang: https://godbolt.org/z/Phc47Wcaa
Я понимаю, что оба примера должны быть эквивалентными, поскольку static_lib использует Dynamic_lib в режиме только заголовка, и поэтому определение dynamic_func
в конечном итоге появляется в одной и той же единице перевода. Использование inline
, похоже, ничего не меняет. Однако, анализируя предварительно обработанный вывод static_lib.cpp, я увидел, что первый пример неудачен из-за объявления dynamic_func
внутри extern "C"
; добавление этого ко второму примеру приводит к сбою так же, как и к первому.
Это удивительно, поскольку я не знаю, почему extern "C"
играет здесь какую-либо роль. Я что-то неправильно понимаю?
Жаль, что инструмент обнаружения нарушений ODR ORC не работает на этой платформе. :садпанда:
Как указал @rustyx в одном из комментариев, похоже, что extern "C"
действительно влияет на имена символов, даже если они находятся в глобальном пространстве имен. Удаление extern "C"
из всех объявлений также вызывает ошибку компоновщика. Как сказал @rustyx, смешивание объявлений extern "C"
и no-extern "C"
создает дублирующиеся функции, которые могут сосуществовать.
1) Является ли функция
extern "C"
или нет, определяется во время первого объявления. 2) Смешение определенийextern "C"
и отсутствияextern "C"
приведет к созданию дублирующих функций, которые могут сосуществовать, поскольку правила оформления имен разные. Попробуйте сбросить символы DLL.