C - альтернатива #ifdef - PullRequest
       69

C - альтернатива #ifdef

4 голосов
/ 27 октября 2009

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

Пример следует, но вот вопрос. Я ржавый на моем C , но я помню, что использование #ifdef не рекомендуется. Ребята, вы можете предложить лучшую альтернативу? Кроме того - я думаю, что некоторые из них (если не все) могут быть установлены как переменные среды или переданы в качестве параметра, и если это так - что будет хорошим способом определения их и последующего доступа из исходного кода?

Вот фрагмент кода, с которым я имею дело

#define DAN          NO
#define UNIX         NO
#define LINUX        YES
#define WINDOWS_ES   NO
#define WINDOWS_RB   NO

/* Later in the code */
#if ((DAN==1) || (UNIX==YES))
#include <sys/param.h>
#endif

#if ((WINDOWS_ES==YES) || (WINDOWS_RB==YES) || (WINDOWS_TIES==YES))
#include <param.h>
#include <io.h>
#include <ctype.h>
#endif

/* And totally insane harcoded paths */
#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/test/MasterSkipFile";
#endif

#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif

#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif

/* So on for every platform and combination */

Ответы [ 7 ]

13 голосов
/ 27 октября 2009

Конечно, вы можете передать -DWHATEVER в командной строке. Или -DWHATEVER_ELSE=NO и т. Д. Может быть, для путей вы могли бы сделать что-то вроде

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

, а затем передать

-DSOME_COMMAND_LINE_DEFINITION="/home/whatever/directory/filename"

в командной строке.

5 голосов
/ 27 октября 2009

Одна вещь, которую мы использовали, - это сгенерированный файл .h с этими определениями и сгенерированный сценарий. Это помогло нам избавиться от множества хрупких #ifs и # ifdefs

Вы должны быть осторожны с тем, что вы там указали, но параметры машины являются хорошими кандидатами - так работает autoconf / automake.

РЕДАКТИРОВАТЬ: в вашем случае, пример будет использовать сгенерированный файл .h для определения INCLUDE_SYS_PARAM и INCLUDE_PARAM, а в самом коде используйте:

#ifdef INCLUDE_SYS_PARAM
#include <sys/param.h>
#endif

#ifdef INCLUDE_PARAM
#include <param.h>
#endif

Упрощает перенос на новые платформы - существование новой платформы не влияет на код, а только на сгенерированный файл .h.

4 голосов
/ 27 октября 2009

Заголовки конфигурации для конкретной платформы

У меня была бы система для генерации специфичной для платформы конфигурации в заголовок, который используется во всех сборках. Имя AutoConf - «config.h»; Вы можете увидеть «platform.h» или «porting.h» или «port.h» или другие варианты темы. Этот файл содержит информацию, необходимую для построения платформы. Вы можете сгенерировать файл, скопировав вариант, зависящий от версии, в стандартное имя. Вы можете использовать ссылку вместо копирования. Или вы можете запустить конфигурационные сценарии, чтобы определить его содержимое на основе того, что сценарий найден на компьютере.

Значения по умолчанию для параметров конфигурации

Код:

#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/MasterSkipFile";
#endif

#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif

#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif

Лучше бы заменить на:

#ifndef MASTER_SKIP_FILE_PATH
#define MASTER_SKIP_FILE_PATH "/opt/tretools/MasterSkipFile"
#endif

const char MasterSkipFile[] = MASTER_SKIP_FILE_PATH;

Те, кто хочет построить сборку в другом месте, могут установить местоположение с помощью:

-DMASTER_SKIP_FILE_PATH='"/ptehome/tregtp/tre1/tretools/PinkElephant"'

Обратите внимание на использование одинарных и двойных кавычек; старайтесь не делать этого в командной строке с обратными слешами в пути. Вы можете использовать подобный механизм по умолчанию для всех видов вещей:

#ifndef DEFAULTABLE_PARAMETER
#define DEFAULTABLE_PARAMETER default_value
#endif

Если вы выберете свои значения по умолчанию, это может сэкономить много энергии.

Перемещаемое программное обеспечение

Я не уверен насчет дизайна программного обеспечения, которое может быть установлено только в одном месте. В моей книге вы должны иметь возможность установить на машину старую версию 1.12 продукта одновременно с новой версией 2.1, и они должны работать независимо друг от друга. Жестко заданное имя пути побеждает это.

Параметризация по функции, а не по платформе

Ключевое различие между инструментами AutoConf и средней альтернативной системой заключается в том, что конфигурация выполняется на основе функций, а не на платформах. Вы параметризуете свой код, чтобы определить функцию, которую вы хотите использовать. Это очень важно, поскольку функции, как правило, появляются на платформах, отличных от оригинала. Я смотрю после кода, где есть такие строки, как:

#if defined(SUN4) || defined(SOLARIS_2) || defined(HP_UX) || \
    defined(LINUX) || defined(PYRAMID) || defined(SEQUENT) || \
    defined(SEQUENT40) || defined(NCR) ...
#include <sys/types.h>
#endif

Было бы намного лучше иметь:

#ifdef INCLUDE_SYS_TYPES_H
#include <sys/types.h>
#endif

А затем на платформах, где это необходимо, сгенерировать:

#define INCLUDE_SYS_TYPES_H

(Не воспринимайте этот пример заголовок слишком буквально; это концепция, которую я пытаюсь преодолеть.)

Рассматривайте платформу как набор функций

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

Функции продукта должны быть включены в заголовок

( Развивая комментарий, который я сделал к другому ответу. )

Предположим, у вас есть несколько функций в продукте, которые необходимо включить или исключить условно. Например:

KVLOCKING
B1SECURITY
C2SECURITY
DYNAMICLOCKS

Соответствующий код включается при установке соответствующего определения:

#ifdef KVLOCKING
...KVLOCKING stuff...
#else
...non-KVLOCKING stuff...
#endif

Если вы используете инструмент анализа исходного кода, например cscope , то будет полезно, если он покажет вам, когда определен KVLOCKING. Если единственное место, где он определен, находится в некоторых случайных файлах Makefile, разбросанных по системе сборки (предположим, что в этом документе используется сотня подкаталогов), трудно сказать, по-прежнему ли используется код на любом из ваши платформы. Если определения находятся где-то в заголовке - в заголовке для конкретной платформы или, возможно, в заголовке выпуска продукта (поэтому версия 1.x может иметь KVLOCKING, а версия 2.x может включать C2SECURITY, но 2.5 включает B1SECURITY и т. Д.), Тогда вы можете увидеть, что Код KVLOCKING все еще используется.

Поверьте мне, после двадцати лет разработки и текучести кадров люди не знают, используются ли функции по-прежнему или нет (потому что он стабилен и никогда не вызывает проблем - возможно, потому, что он никогда не используется). И если единственное место, где можно узнать, определен ли еще KVLOCKING, находится в файлах Makefile, то такие инструменты, как cscope, менее полезны, что делает изменение кода более подверженным ошибкам при попытке очистки позже.

3 голосов
/ 27 октября 2009

Мой предпочтительный способ - заставить систему сборки обнаруживать ОС.Сложные случаи, когда вы хотите изолировать специфичные для машины данные в одном исходном файле и иметь совершенно разные исходные файлы для разных ОС.

Так что в этом случае у вас будет #include "OS_Specific.h" в этом файле.Вы помещаете различные включения и определение MasterSkipFile для этой платформы.Вы можете выбрать между ними, указав различные -I (включая каталоги путей) в командной строке компилятора.

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

3 голосов
/ 27 октября 2009

Гораздо разумнее использовать:

#if SOMETHING

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

Условная компиляция, учитывая множество операционных систем и их вариантов, является неизбежным злом. если, ifdef и т. д. решительно не злоупотребление препроцессором, просто используйте его как задумано.

2 голосов
/ 27 октября 2009

Я видел системы сборки, в которых большинство исходных файлов начинали примерно так:

#include PLATFORM_CONFIG
#include BUILD_CONFIG

и компилятор был запущен с:

cc -DPLATFORM_CONFIG="linuxconfig.h" -DBUILD_CONFIG="importonlyconfig.h"

(может потребоваться экранирование от обратной косой черты)

это позволило вам разделить настройки платформы в одном наборе файлов и настройки конфигурации в другом. Настройки платформы управляют обработкой библиотечных вызовов, которые могут отсутствовать на одной платформе или не в нужном формате, а также определением важных зависящих от размера типов - вещей, которые зависят от платформы. Настройки сборки определяют, какие функции включены в выводе.

1 голос
/ 28 октября 2009

1001 * Общие положения * Я еретик, который был изгнан из Церкви Автоинструментов GNU. Зачем? Потому что мне нравится понимать, что, черт возьми, делают мои инструменты. И поскольку у меня был опыт попытки объединить два компонента, каждый из которых настаивал на том, чтобы на моем компьютере была установлена ​​несовместимая версия автоинструментов, являющаяся версией по умолчанию. Я работаю, создавая один файл .h или .c для каждой комбинации платформы и существенной абстракции. Я усердно работаю, чтобы определить центральный файл .h, в котором говорится, что такое интерфейс . Часто это означает, что я создаю «слой совместимости», который изолирует меня от различий между платформами. Часто, когда это возможно, я использую ANSI Standard C вместо функциональности, специфичной для платформы. Я иногда пишу скрипты для генерации платформо-зависимых файлов. Но сценарии всегда пишутся от руки и документируются, поэтому я знаю, что они делают. Я восхищаюсь nmake Гленна Фаулера и iffe Фонга Во (если такая функция существует), которые, как мне кажется, лучше разработаны, чем инструменты GNU. Но эти инструменты являются частью пакета программных технологий AT & T, и я не смог понять, как их использовать, не покупая весь AST-метод, который я не всегда понимаю. Ваш пример

Там явно должно быть

extern char MasterSkipFile[];

где-то в файле .h, и затем вы можете создать ссылку на подходящий файл .o.

Условное включение «правильного набора файлов .h для платформы» - это то, что я бы обработал, пытаясь придерживаться ANSI C, когда это возможно, а когда невозможно, определяя уровень совместимости в платформе .h файл. На самом деле, я не могу сказать, какие имена пытаются импортировать #include, поэтому я не могу дать более конкретный совет.

...