Используя #ifdef
вместо написания переносимого кода, вы по-прежнему пишете несколько частей кода, специфичного для платформы.К сожалению, во многих (большинстве?) Случаях вы быстро получаете почти непроницаемую смесь переносимого и специфичного для платформы кода.
Вы также часто получаете #ifdef
, который используется для целей, отличных от переносимости (определяя, что«версия» создаваемого кода, например, какой уровень самодиагностики будет включен).К сожалению, оба часто взаимодействуют и переплетаются.Например, кто-то портирует некоторый код на MacOS и решает, что ему нужно лучше сообщать об ошибках, что он добавляет, но делает это специфичным для MacOS.Позже, кто-то другой решает, что лучшее сообщение об ошибках было бы очень полезно в Windows, поэтому он включает этот код, автоматически #define
используя MACOS, если определен WIN32, но затем добавляет «еще пару» #ifdef WIN32
, чтобы исключить некоторыекод, который действительно является специфичным для MacOS при определении Win32.Конечно, мы также добавляем тот факт, что MacOS основан на BSD Unix, поэтому, когда MACOS определен, он также автоматически определяет BSD_44 - но (снова) оборачивается и исключает некоторые BSD «вещи»при компиляции для MacOS.
Это быстро вырождается в код, подобный следующему примеру (взято из # ifdef Считается вредным ):
#ifdef SYSLOG
#ifdef BSD_42
openlog("nntpxfer", LOG_PID);
#else
openlog("nntpxfer", LOG_PID, SYSLOG);
#endif
#endif
#ifdef DBM
if (dbminit(HISTORY_FILE) < 0)
{
#ifdef SYSLOG
syslog(LOG_ERR,"couldn’t open history file: %m");
#else
perror("nntpxfer: couldn’t open history file");
#endif
exit(1);
}
#endif
#ifdef NDBM
if ((db = dbm_open(HISTORY_FILE, O_RDONLY, 0)) == NULL)
{
#ifdef SYSLOG
syslog(LOG_ERR,"couldn’t open history file: %m");
#else
perror("nntpxfer: couldn’t open history file");
#endif
exit(1);
}
#endif
if ((server = get_tcp_conn(argv[1],"nntp")) < 0)
{
#ifdef SYSLOG
syslog(LOG_ERR,"could not open socket: %m");
#else
perror("nntpxfer: could not open socket");
#endif
exit(1);
}
if ((rd_fp = fdopen(server,"r")) == (FILE *) 0){
#ifdef SYSLOG
syslog(LOG_ERR,"could not fdopen socket: %m");
#else
perror("nntpxfer: could not fdopen socket");
#endif
exit(1);
}
#ifdef SYSLOG
syslog(LOG_DEBUG,"connected to nntp server at %s", argv[1]);
#endif
#ifdef DEBUG
printf("connected to nntp server at %s\n", argv[1]);
#endif
/*
* ok, at this point we’re connected to the nntp daemon
* at the distant host.
*/
Это довольно небольшой примерпри этом задействовано всего лишь несколько макросов, но чтение кода уже является болезненным.Я лично видел (и должен был иметь дело) намного хуже в реальном коде.Здесь код уродлив и болезнен для чтения, но все еще довольно легко определить, какой код будет использоваться при каких обстоятельствах.Во многих случаях вы получаете гораздо более сложные структуры.
Чтобы привести конкретный пример того, как я предпочел бы видеть написанное, я бы сделал что-то вроде этого:
if (!open_history(HISTORY_FILE)) {
logerr(LOG_ERR, "couldn't open history file");
exit(1);
}
if ((server = get_nntp_connection(server)) == NULL) {
logerr(LOG_ERR, "couldn't open socket");
exit(1);
}
logerr(LOG_DEBUG, "connected to server %s", argv[1]);
В таком случае возможно , что наше определение logerr будет макросом вместо реальной функции.Может быть достаточно тривиально, чтобы иметь смысл иметь заголовок с чем-то вроде:
#ifdef SYSLOG
#define logerr(level, msg, ...) /* ... */
#else
enum {LOG_DEBUG, LOG_ERR};
#define logerr(level, msg, ...) /* ... */
#endif
[на данный момент, предполагая, что препроцессор может / будет обрабатывать переменные макросы]
Учитываяотношение вашего руководителя, даже если может быть неприемлемым.Если так, это нормально.Вместо макроса, реализуйте эту возможность в функции.Изолируйте каждую реализацию функции (ей) в своем собственном исходном файле и постройте файлы, соответствующие цели.Если у вас много платформо-зависимого кода, вы обычно хотите изолировать его в отдельный каталог, вполне возможно, с собственным make-файлом 1 , и иметь make-файл верхнего уровня, который просто выбирает, какой другойmake-файлы для вызова на основе указанной цели.
- Некоторые люди предпочитают этого не делать.Я не спорю так или иначе о том, как структурировать make-файлы, просто отмечаю, что некоторые люди считают / считают это полезным.