Включение файлов с ifdef приводит к нескольким определениям

#c #include #linker-errors

#c #включить #компоновщик-ошибки

Вопрос:

Я пытаюсь понять ifdef директиву , включая файлы, и, хотя мне показалось, что это имеет смысл в моей голове, кажется, это не так просто, как я думал: я получаю ошибку «несколько определений».

То, что я пытаюсь сделать, это заставить компилятор включить определенный файл в зависимости от того, определено ли что-то.

У меня есть a.cpp , и b.cpp оба из них имеют одну и ту же функцию, но немного другую логику:

a.cpp

 int aFunction(int apples){
    return apples * 10;
}
  

b.cpp

 int aFunction(int apples){
    return apples * 0.1;
}
  

В основном я пытаюсь это сделать:

 #ifdef USE_B
#pragma message "Using b"
#include "b.cpp"
#else
#pragma message "Using a"
#include "a.cpp"
#endif
  

Затем в моем реальном коде вызовите его

 aFunction(5);
  

Я предполагал, что компилятор прочитает эти директивы и выберет либо a.cpp или b.cpp , и, таким образом, соответствующий исходный код будет содержать только реализацию / экземпляр aFunction() .

Но это делает что-то, что я не совсем понимаю, и приводит к ошибке «несколько определений».

Подробнее об ошибке ниже:

 sketchb.cpp.o (symbol from plugin): In function `aFunction(int)':

(.text 0x0): multiple definition of `aFunction(int)'

sketcha.cpp.o (symbol from plugin):(.text 0x0): first defined here
  

Не совсем уверен, почему он даже смотрит на a.cpp то, когда USB_B определено.

Я определил это, перейдя:

 #define USE_B 1
  

(Примечание: не уверен, имеет ли это значение, но я работаю в среде arduino).



Добавлены мысли / рассуждения

Кажется, я немного запутался в процессах предварительной обработки, компиляции и компоновки. Я признаю, что это не то, с чем я слишком хорошо знаком. (Спасибо за очистку моих тегов, которые напомнили мне об этом)

Для меня это работает и соответствует моим целям:

 #define USE_B 1

#ifdef USE_B
#pragma message "Using b"
int aFunction(int apples){      return apples * 0.1;    } // contents of b.cpp
#else
#pragma message "Using a"
int aFunction(int apples){      return apples * 10;     } // contents of a.cpp
#endif

void main(){
    aFunction(5);
}
  

Если я определяю USE_B , то только версия ‘b’ aFunction попадает в сгенерированный источник (я предполагаю, после процесса препроцессора) и, таким образом, приводит к тому, что компилируется только одна из версий aFunction .

Насколько я понимаю, #include директива работает так, как если бы она «копировала и вставляла» содержимое включенного файла туда, где #include находится директива. Возможно, я здесь ошибаюсь? Это в основном то, откуда возникает моя путаница, если #include работать путем копирования содержимого a.cpp и b.cpp в мой основной файл, разве я не должен получить что-то похожее на вышеупомянутое? (что, опять же, отлично работает для меня)

Комментарии:

1. вы также безоговорочно связываете оба a.cpp и b.cpp .

2. Чего вы пытаетесь достичь (помимо выбора одной версии aFunction )?

3. Как правило, не включайте файлы source ( .cpp ) . Используйте какой-нибудь инструмент управления проектами для условной сборки файлов.

4. В качестве вероятной причины вашей проблемы препроцессор и его макросы являются отдельным языком от C , и как таковой он на самом деле не имеет операторов. Это означает, что вы не завершаете макросы точкой с запятой.

5. Если вы посмотрите на сообщение об ошибке, оба a.cpp и b.cpp используются для сборки вашего исполняемого файла. Вы не только включаете файлы, вы также создаете объектные файлы из этих исходных файлов и связываетесь с объектными файлами. Вам нужно выбрать один подход (где моя рекомендация — условное построение и связывание, а не включение).

Ответ №1:

Что вы можете сделать вместо #include редактирования исходных файлов, так это тот же трюк, который Microsoft использует для привязки к их двум версиям API ( xxxA() , xxxW() функции):

 #ifdef USE_B
#pragma message "Using b"
#define aFunction aFunctionB
#else
#pragma message "Using a"
#define aFunction aFunctionA
#endif
  

и в реализации

a.cpp

 int aFunctionA(int apples){
    return apples * 10;
}
  

b.cpp

 int aFunctionB(int apples){
    return apples * 0.1;
}
  

и в вашем реальном коде вызовите его

 aFunction(5);
  

Комментарии:

1. Будут ли версии aFunctionA и aFunctionB тогда будут выполнены? В конце я пытаюсь ограничить объем использования пространства / памяти. Мне это нравится, хотя, по крайней мере, это решает проблему именования.

2. @mitim Это должно регулироваться в вашей системе сборки, а не в коде. В любом случае компоновщик удалит невыбранные символы, но вам все равно придется потратить время компиляции, используемое для обеих реализаций, если вы не разберетесь с этим на этапе компиляции.

Ответ №2:

После некоторого изучения кажется, что во всем процессе есть что-то, чего я не понимаю. В любом случае, также кажется, что консенсус (из ответов здесь и того, что я могу прочитать в другом месте) заключается в том, что использование препроцессора на самом деле не является правильным способом делать то, что я хочу, и следует использовать систему сборки.

По общему признанию, это все еще немного странно на мой взгляд, но, возможно, однажды я это увижу. Поскольку проект небольшой, я думаю, я просто напишу для этого простой скрипт сборки.