C++: ODR и extern "C" в компоновщике MSVC

Я проверяю, как программа 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" играет здесь какую-либо роль. Я что-то неправильно понимаю?

1) Является ли функция extern "C" или нет, определяется во время первого объявления. 2) Смешение определений extern "C" и отсутствия extern "C" приведет к созданию дублирующих функций, которые могут сосуществовать, поскольку правила оформления имен разные. Попробуйте сбросить символы DLL.

rustyx 18.07.2024 16:42

Жаль, что инструмент обнаружения нарушений ODR ORC не работает на этой платформе. :садпанда:

Eljay 18.07.2024 17:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как указал @rustyx в одном из комментариев, похоже, что extern "C" действительно влияет на имена символов, даже если они находятся в глобальном пространстве имен. Удаление extern "C" из всех объявлений также вызывает ошибку компоновщика. Как сказал @rustyx, смешивание объявлений extern "C" и no-extern "C" создает дублирующиеся функции, которые могут сосуществовать.

Другие вопросы по теме

Похожие вопросы

Сортировка слиянием для односвязных списков дает правильные результаты, но приводит к утечке памяти
Генерировать номер из обратносвязного списка
Как вернуть умные указатели и ковариацию в C++
Выполняется ли оператор в C++, то есть инструкции, заканчивающиеся точкой с запятой ';', целиком в многопоточном контексте?
Назначить базовый объект, не меняя унаследованные?
Почему я получаю сообщение «объявление for не относится к классу, шаблону класса или частичной специализации шаблона класса» в Clang, а не в GCC
При вызове метода, унаследованного от производного класса, он пытается использовать неинициализированные поля базового класса
Частичная специализация класса для указателя на члены
Неопределенная ссылка с выражением require с использованием clang++
GCC 14 «возможно, висячая ссылка на временное» предупреждение или нет, в зависимости от аргумента функции