Сразу предупреждаю, мои вкусы очень специфичны. Красота ошибки в том, что человеку её очень сложно найти. Я не верю, что её можно заметить при обзоре кода. Если только заранее знать, что она есть, и искать её целенаправленно.
Ошибку я нашёл в проекте DPDK. В нём есть и другие ошибки, но про них потом. Они меркнут перед этим алмазом. Только не ждите чего-то эдакого. Ошибка проста до безобразия. Вот только найти её, просматривая код, ой как непросто. Собственно, попробуйте сами.
Для этого сначала изучите список именованных констант:
enum dbg_status {
DBG_STATUS_OK,
DBG_STATUS_APP_VERSION_NOT_SET,
DBG_STATUS_UNSUPPORTED_APP_VERSION,
DBG_STATUS_DBG_BLOCK_NOT_RESET,
DBG_STATUS_INVALID_ARGS,
DBG_STATUS_OUTPUT_ALREADY_SET,
DBG_STATUS_INVALID_PCI_BUF_SIZE,
DBG_STATUS_PCI_BUF_ALLOC_FAILED,
DBG_STATUS_PCI_BUF_NOT_ALLOCATED,
DBG_STATUS_INVALID_FILTER_TRIGGER_DWORDS,
DBG_STATUS_NO_MATCHING_FRAMING_MODE,
DBG_STATUS_VFC_READ_ERROR,
DBG_STATUS_STORM_ALREADY_ENABLED,
DBG_STATUS_STORM_NOT_ENABLED,
DBG_STATUS_BLOCK_ALREADY_ENABLED,
DBG_STATUS_BLOCK_NOT_ENABLED,
DBG_STATUS_NO_INPUT_ENABLED,
DBG_STATUS_NO_FILTER_TRIGGER_256B,
DBG_STATUS_FILTER_ALREADY_ENABLED,
DBG_STATUS_TRIGGER_ALREADY_ENABLED,
DBG_STATUS_TRIGGER_NOT_ENABLED,
DBG_STATUS_CANT_ADD_CONSTRAINT,
DBG_STATUS_TOO_MANY_TRIGGER_STATES,
DBG_STATUS_TOO_MANY_CONSTRAINTS,
DBG_STATUS_RECORDING_NOT_STARTED,
DBG_STATUS_DATA_DIDNT_TRIGGER,
DBG_STATUS_NO_DATA_RECORDED,
DBG_STATUS_DUMP_BUF_TOO_SMALL,
DBG_STATUS_DUMP_NOT_CHUNK_ALIGNED,
DBG_STATUS_UNKNOWN_CHIP,
DBG_STATUS_VIRT_MEM_ALLOC_FAILED,
DBG_STATUS_BLOCK_IN_RESET,
DBG_STATUS_INVALID_TRACE_SIGNATURE,
DBG_STATUS_INVALID_NVRAM_BUNDLE,
DBG_STATUS_NVRAM_GET_IMAGE_FAILED,
DBG_STATUS_NON_ALIGNED_NVRAM_IMAGE,
DBG_STATUS_NVRAM_READ_FAILED,
DBG_STATUS_IDLE_CHK_PARSE_FAILED,
DBG_STATUS_MCP_TRACE_BAD_DATA,
DBG_STATUS_MCP_TRACE_NO_META,
DBG_STATUS_MCP_COULD_NOT_HALT,
DBG_STATUS_MCP_COULD_NOT_RESUME,
DBG_STATUS_RESERVED0,
DBG_STATUS_SEMI_FIFO_NOT_EMPTY,
DBG_STATUS_IGU_FIFO_BAD_DATA,
DBG_STATUS_MCP_COULD_NOT_MASK_PRTY,
DBG_STATUS_FW_ASSERTS_PARSE_FAILED,
DBG_STATUS_REG_FIFO_BAD_DATA,
DBG_STATUS_PROTECTION_OVERRIDE_BAD_DATA,
DBG_STATUS_DBG_ARRAY_NOT_SET,
DBG_STATUS_RESERVED1,
DBG_STATUS_NON_MATCHING_LINES,
DBG_STATUS_INSUFFICIENT_HW_IDS,
DBG_STATUS_DBG_BUS_IN_USE,
DBG_STATUS_INVALID_STORM_DBG_MODE,
DBG_STATUS_OTHER_ENGINE_BB_ONLY,
DBG_STATUS_FILTER_SINGLE_HW_ID,
DBG_STATUS_TRIGGER_SINGLE_HW_ID,
DBG_STATUS_MISSING_TRIGGER_STATE_STORM,
MAX_DBG_STATUS
};
Теперь изучите массив строк:
static const char * const s_status_str[] = {
/* DBG_STATUS_OK */
"Operation completed successfully",
/* DBG_STATUS_APP_VERSION_NOT_SET */
"Debug application version wasn't set",
/* DBG_STATUS_UNSUPPORTED_APP_VERSION */
"Unsupported debug application version",
/* DBG_STATUS_DBG_BLOCK_NOT_RESET */
"The debug block wasn't reset since the last recording",
/* DBG_STATUS_INVALID_ARGS */
"Invalid arguments",
/* DBG_STATUS_OUTPUT_ALREADY_SET */
"The debug output was already set",
/* DBG_STATUS_INVALID_PCI_BUF_SIZE */
"Invalid PCI buffer size",
/* DBG_STATUS_PCI_BUF_ALLOC_FAILED */
"PCI buffer allocation failed",
/* DBG_STATUS_PCI_BUF_NOT_ALLOCATED */
"A PCI buffer wasn't allocated",
/* DBG_STATUS_INVALID_FILTER_TRIGGER_DWORDS */
"The filter/trigger constraint dword offsets are not "
"enabled for recording",
/* DBG_STATUS_VFC_READ_ERROR */
"Error reading from VFC",
/* DBG_STATUS_STORM_ALREADY_ENABLED */
"The Storm was already enabled",
/* DBG_STATUS_STORM_NOT_ENABLED */
"The specified Storm wasn't enabled",
/* DBG_STATUS_BLOCK_ALREADY_ENABLED */
"The block was already enabled",
/* DBG_STATUS_BLOCK_NOT_ENABLED */
"The specified block wasn't enabled",
/* DBG_STATUS_NO_INPUT_ENABLED */
"No input was enabled for recording",
/* DBG_STATUS_NO_FILTER_TRIGGER_256B */
"Filters and triggers are not allowed in E4 256-bit mode",
/* DBG_STATUS_FILTER_ALREADY_ENABLED */
"The filter was already enabled",
/* DBG_STATUS_TRIGGER_ALREADY_ENABLED */
"The trigger was already enabled",
/* DBG_STATUS_TRIGGER_NOT_ENABLED */
"The trigger wasn't enabled",
/* DBG_STATUS_CANT_ADD_CONSTRAINT */
"A constraint can be added only after a filter was "
"enabled or a trigger state was added",
/* DBG_STATUS_TOO_MANY_TRIGGER_STATES */
"Cannot add more than 3 trigger states",
/* DBG_STATUS_TOO_MANY_CONSTRAINTS */
"Cannot add more than 4 constraints per filter or trigger state",
/* DBG_STATUS_RECORDING_NOT_STARTED */
"The recording wasn't started",
/* DBG_STATUS_DATA_DID_NOT_TRIGGER */
"A trigger was configured, but it didn't trigger",
/* DBG_STATUS_NO_DATA_RECORDED */
"No data was recorded",
/* DBG_STATUS_DUMP_BUF_TOO_SMALL */
"Dump buffer is too small",
/* DBG_STATUS_DUMP_NOT_CHUNK_ALIGNED */
"Dumped data is not aligned to chunks",
/* DBG_STATUS_UNKNOWN_CHIP */
"Unknown chip",
/* DBG_STATUS_VIRT_MEM_ALLOC_FAILED */
"Failed allocating virtual memory",
/* DBG_STATUS_BLOCK_IN_RESET */
"The input block is in reset",
/* DBG_STATUS_INVALID_TRACE_SIGNATURE */
"Invalid MCP trace signature found in NVRAM",
/* DBG_STATUS_INVALID_NVRAM_BUNDLE */
"Invalid bundle ID found in NVRAM",
/* DBG_STATUS_NVRAM_GET_IMAGE_FAILED */
"Failed getting NVRAM image",
/* DBG_STATUS_NON_ALIGNED_NVRAM_IMAGE */
"NVRAM image is not dword-aligned",
/* DBG_STATUS_NVRAM_READ_FAILED */
"Failed reading from NVRAM",
/* DBG_STATUS_IDLE_CHK_PARSE_FAILED */
"Idle check parsing failed",
/* DBG_STATUS_MCP_TRACE_BAD_DATA */
"MCP Trace data is corrupt",
/* DBG_STATUS_MCP_TRACE_NO_META */
"Dump doesn't contain meta data - it must be provided in image file",
/* DBG_STATUS_MCP_COULD_NOT_HALT */
"Failed to halt MCP",
/* DBG_STATUS_MCP_COULD_NOT_RESUME */
"Failed to resume MCP after halt",
/* DBG_STATUS_RESERVED0 */
"",
/* DBG_STATUS_SEMI_FIFO_NOT_EMPTY */
"Failed to empty SEMI sync FIFO",
/* DBG_STATUS_IGU_FIFO_BAD_DATA */
"IGU FIFO data is corrupt",
/* DBG_STATUS_MCP_COULD_NOT_MASK_PRTY */
"MCP failed to mask parities",
/* DBG_STATUS_FW_ASSERTS_PARSE_FAILED */
"FW Asserts parsing failed",
/* DBG_STATUS_REG_FIFO_BAD_DATA */
"GRC FIFO data is corrupt",
/* DBG_STATUS_PROTECTION_OVERRIDE_BAD_DATA */
"Protection Override data is corrupt",
/* DBG_STATUS_DBG_ARRAY_NOT_SET */
"Debug arrays were not set "
"(when using binary files, dbg_set_bin_ptr must be called)",
/* DBG_STATUS_RESERVED1 */
"",
/* DBG_STATUS_NON_MATCHING_LINES */
"Non-matching debug lines - in E4, all lines must be of "
"the same type (either 128b or 256b)",
/* DBG_STATUS_INSUFFICIENT_HW_IDS */
"Insufficient HW IDs. Try to record less Storms/blocks",
/* DBG_STATUS_DBG_BUS_IN_USE */
"The debug bus is in use",
/* DBG_STATUS_INVALID_STORM_DBG_MODE */
"The storm debug mode is not supported in the current chip",
/* DBG_STATUS_OTHER_ENGINE_BB_ONLY */
"Other engine is supported only in BB",
/* DBG_STATUS_FILTER_SINGLE_HW_ID */
"The configured filter mode requires a single Storm/block input",
/* DBG_STATUS_TRIGGER_SINGLE_HW_ID */
"The configured filter mode requires that all the constraints of a "
"single trigger state will be defined on a single Storm/block input",
/* DBG_STATUS_MISSING_TRIGGER_STATE_STORM */
"When triggering on Storm data, the Storm to trigger on must be specified"
};
Последнее — код, который превращает именованную константу в строчку:
const char *qed_dbg_get_status_str(enum dbg_status status)
{
return (status < MAX_DBG_STATUS) ?
s_status_str[status] : "Invalid debug status";
}
Где ошибка?
Нашли?
На самом деле есть намёк, за который можно теоретически зацепиться. В массиве все строки отделяются одной пустой строкой (см. первый комментарий к статье), кроме одного места. Но начнём мы с предупреждения анализатора кода PVS-Studio:
V557 Array overrun is possible. The value of 'status' index could reach 58. qede_debug.c 7149
Оно указывает на место, где из массива по индексу извлекается указатель на строку:
return (status < MAX_DBG_STATUS) ?
s_status_str[status] : "Invalid debug status";
Вначале я поставил неправильный скучный диагноз. По невнимательности я подумал, что передо мной off-by-one error. Уж очень это похоже на типовую неправильную проверку, которую я встречал много раз в разных проектах.
Суть классического антипаттерна. Есть enum, последний элемент которого используется как количество элементов в нём.
enum Efoo {
A,
B,
C,
COUNT
};
Константам в enum присваиваются значения, начиная с 0. Соответственно, вспомогательная константа COUNT окажется равна 3, что соответствует количеству основных именованных констант.
Есть какой-то массив, где каждой именованной константе что-то соответствует:
char Cfoo[] = { 'A', 'B', 'C' };
Часто допускают баг, когда проверяют, что индекс не выходит за границу массива:
char Convert(unsigned id)
{
return (id > COUNT) ? 0 : Cfoo[id];
}
Проверка работает неправильно: если id окажется равен COUNT, то произойдёт выход за границу массива. Для таких ошибок анализатор PVS-Studio выдаёт предупреждение аналогично рассмотренному ранее.
Правильным будет один из следующих вариантов:
return (id >= COUNT) ? 0 : Cfoo[id]; // OK
return (id < COUNT) ? Cfoo[id] : 0; // OK
Ничего интересного. Расходимся. В статью можно выписывать только само предупреждение рядом с другими ошибками выхода за границу, но сам баг не разбирать в статье про проверку DPDK.
И тут в последний момент — СТОП!
ЗДЕСЬ ЖЕ ВЕДЬ ПРАВИЛЬНАЯ ПРОВЕРКА НАПИСАНА!
return (status < MAX_DBG_STATUS) ?
s_status_str[status] : "Invalid debug status";
Теперь непонятно и интересно! Почему анализатор выдал предупреждение? Ложное срабатывание? Это вряд ли, тут ему негде запутаться.
Засучив рукава, я начинаю внимательно изучать код. Вот оно! Пропущена строка для константы DBG_STATUS_NO_MATCHING_FRAMING_MODE.
В enum:
DBG_STATUS_INVALID_FILTER_TRIGGER_DWORDS,
DBG_STATUS_NO_MATCHING_FRAMING_MODE,
DBG_STATUS_VFC_READ_ERROR,
В массиве:
/* DBG_STATUS_INVALID_FILTER_TRIGGER_DWORDS */
"The filter/trigger constraint dword offsets are not "
"enabled for recording",
/* DBG_STATUS_VFC_READ_ERROR */
"Error reading from VFC",
Обратите внимание на разделитель, состоящий из двух пустых строчек (см. первый комментарий к статье). Я упоминал об этом артефакте раньше. Здесь что-то пошло не так. Возможно, произошёл какой-то сбой при заполнении массива, возможно, строчку случайно удалили, возможно, неудачный мёрж веток, или что-то ещё.
Впрочем, это сейчас, когда найден баг, две пустые строчки выглядят подозрительно. При обзоре никто не обратит на них внимание. А даже случайно заметив, просто удалят одну из них для красоты. Никто не скажет, "да тут небось что-то пропустили, пойдём проверим" :)
В результате этой ошибки:
- возможен выход за границу массива;
- функция возвращает неправильные строки для всех констант, начиная с DBG_STATUS_NO_MATCHING_FRAMING_MODE.
На мой взгляд, это красиво. Спасибо за внимание. Регулярно проверяйте код используя PVS-Studio.
P.S.
Коллега предложил добавить в статью, что подобной ошибки можно избежать, используя проверки этапа компиляции. Примеры кода с static_assert для C и C++. Для C++ получается ловчее с вычислением размеров массивов, но это уже другая история. Пожалуй, везде такие проверки добавлять — перебор. Но вот для таких больших массивов, почему бы и нет.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Most striking error I found with PVS-Studio in 2024.
Комментарии (44)
RodionGork
28.10.2024 07:33Ну такое, всё же здесь пресловутая "ошибка в ДНК" скорее, а то что нашли - уже вторичное проявление. Безусловно если код разрабатывать таким образом то без тулов для всевозможных проверок и анализа будет очень больно.
Если уж константы определили то нужно было коненчо не лениться и строки расставить в соответствии с ними, а не комменты к ним приписывать, т.е. массив инициализировать вот таким немного унылым образом:
s_status_str[DBG_STATUS_OK] = "Operation completed successfully"; s_status_str[DBG_STATUS_APP_VERSION_NOT_SET] = "Debug application version wasn't set"; //...
я не знаток C/C++ так что может есть уже какая-то более удобная конструкция (можно было вообще ключи текстовые имхо и мапу использовать) - но принцип такой - не надо константы и значения по двум разным файлам разносить и пытаться их вручную с дурацкими комментами матчить. это обязательно "бумкнет" :)
alexxisr
28.10.2024 07:33в джаве сделали удобно - можно в самом enum добавлять произвольные поля и пользоваться им как этакой нерасширяемой мапой.
RodionGork
28.10.2024 07:33да, вариантов много - ну в джаве можно было бы имя константы самой вывести с помощью рефлексии и не возиться со строчками. или вынести строчки в json-файл и выбирать их оттуда (возможно опять же по имени константы)... тут и локализацию легко внедрить.
mentin
28.10.2024 07:33Проблема с такой инициализацией - она выполняется в рантайме, а та - статически компилятором. Начиная с С99 (то есть почти везде) можно и статически, но про это мало кто знает и редко использует.
https://www.geeksforgeeks.org/designated-initializers-c/RodionGork
28.10.2024 07:33но про это мало кто знает
спасибо, огонь :) очевидно "красивая ошибка" настолько стара что ещё в C99 против неё попытались обезопаситься
DrGluck07
28.10.2024 07:33А ещё эту инициализацию можно делать в любом порядке. Хоть это наверное и не очень правильно.
DrGluck07
28.10.2024 07:33Всегда так делаю. Кстати, забавно, что многие программисты C++ не знают о таком способе инициализации массивов.
zzzzzzerg
28.10.2024 07:33А они должны?
Aggregate initialization - cppreference.com
Note: out-of-order designated initialization, nested designated initialization, mixing of designated initializers and regular initializers, and designated initialization of arrays are all supported in the C programming language, but are not allowed in C++.
DrGluck07
28.10.2024 07:33Не разрешена-то не разрешена, но чот прекрасно работает.
zzzzzzerg
28.10.2024 07:33Наверное у вас в компиляторе есть расширение для этого. Когда последний раз год назад я пробовал в VS2019 - не работало.
DrGluck07
28.10.2024 07:33Мы это в основном на микроконтроллерах используем, там своя атмосфера. Хотя, кмк, в каком-то проекте на Qt тоже использовали и всё было нормально.
Mishootk
28.10.2024 07:33я не знаток C/C++ так что может есть уже какая-то более удобная конструкция (можно было вообще ключи текстовые имхо и мапу использовать) - но принцип такой - не надо константы и значения по двум разным файлам разносить и пытаться их вручную с дурацкими комментами матчить. это обязательно "бумкнет" :)
Вот отправная точка:
https://habr.com/ru/articles/276763/
Взял идею, под свои потребности написал почти заново и нарадоваться не могу.
mentin
28.10.2024 07:33На ревью кода конечно такое можно поймать, я бы здесь попросил добавить static_assert что размер второго массива совпадает с
MAX_DBG_STATUS
.RodionGork
28.10.2024 07:33порядок перепутать легко все равно... особенно если эти фрагменты часто редактируют разные люди... ну тогда и анализатор не поможет впрочем :)
viordash
28.10.2024 07:33можно использовать в
qed_dbg_get_status_str
switch на енамdbg_status
. В этом случае массив строкs_status_str
уже не нужен. А компилятор ругнется если не все кейсы будут обработаны.
COKPOWEHEU
28.10.2024 07:33Да тут сам массив строк выглядит ужасно.
static const char * const s_status_str[] = { [DBG_STATUS_OK] = "Operation completed successfully", [DBG_STATUS_APP_VERSION_NOT_SET] = "Debug application version wasn't set", [DBG_STATUS_UNSUPPORTED_APP_VERSION] = "Unsupported debug application version",
И все.
datacompboy
28.10.2024 07:33Поможет гарантировать, что к правильным индексами привязаны правильные строки, но не гарантирует, что для всех индексов прописаны строки.
Причем, если к последней определённой константе строка есть -- пропущенные в середине countof() не поймает.
COKPOWEHEU
28.10.2024 07:33не гарантирует, что для всех индексов прописаны строки.
А это толком и не отследить. Разве что в рантайме пройтись циклом и проверить длину каждой строки. В любом случае это куда менее опасная ошибка, чем сдвиг вообще всех индексов.
Пример по сути демонстрирует, что используя плохие практики программирования, можно получить трудноуловимые ошибки. Ну так не используйте плохие практики.
datacompboy
28.10.2024 07:33А это толком и не отследить
Есть много способов отследить и убедиться в этом. Как правило, игнорируемые по причине того, что код -- одноразовый. Написал и забыл, редактируется настолько редко, что нагораживание защит вокруг дороже обойдётся.
Лучший способ в данном конкретном случае (и всех аналогичных) -- использовать статический анализатор, который может поймать косвенно (как в статье) по использованию, или попытаться "догадаться" что массивы должны быть 1-в-1 с enum'ом.
Я удивлён, кстати ( @Andrey2008 ), что PVS-Studio не догадывается что это дескриптор для ENUMа и не проверяет на равенство элементов и порядка констант унутре. А ведь косяк в таких массивах часто получается из-за потерянной запятой.
COKPOWEHEU
28.10.2024 07:33Есть много способов отследить и убедиться в этом.
Средствами языка и без накладных расходов? Что-то ничего в голову не приходит.
datacompboy
28.10.2024 07:33Средствами языка и без накладных расходов:
Когда в массиве элементы перечислены последовательно -- сравнивая countof() в compile-time, мой основной подход;
Мета-макросы, как @RR_Zz упоминул ниже;
unit-testing с перечислением всех вариантов (я считаю форменным издевательством для данного случая, впрочем).
Средствами языка с накладными расходами:
Используя enum class + функцию с case вместо массива;
Проверки assert()'ы или просто if()'s в функциях доступа для проверок или включая проверки на доступ вне массива
Средставми вне языка без накладных расходов в рантайме:
стандартные статические анализаторы;
генератор кода по внешнему источнику (в каком-то смысле вариация x-macro)
ручной валидатор кода заточенный под конкретный случай, например, автогенерация юнит теста.
Это навскидку
COKPOWEHEU
28.10.2024 07:33Когда в массиве элементы перечислены последовательно -- сравнивая countof()
Что такое countof? gcc ругается, гугл ссылается куда-то в С++. Или я что-то неправильно понял?
Мета-макросы, как @RR_Zz упоминул ниже;
Интересная штука. Надо будет над ними помедитировать поподробнее.
unit-testing
Это все же не средствами языка
Спасибо. Вариант с мета-макросами интересный, может и пригодится когда.
datacompboy
28.10.2024 07:33sizeof -- размер в байтах, countof -- размер в элементах. Существует как _countof и в вариациях имени типа array_size, ARRAY_SIZE, std::ranges::size и все прочие. Просто проверяем, на момент компиляции, что размер массива сопадает числу элементов ENUMа.
COKPOWEHEU
28.10.2024 07:33Существует как _countof и в вариациях имени типа array_size, ARRAY_SIZE, std::ranges::size
На все эти варианты gcc ругается
undefined reference to `countof'
. Может, в Си этой функции все же нет? Или имеется в виду вообще любая, в том числе самописная, функция / макрос вида(sizeof(x)/sizeof(x[0]))
?Просто проверяем, на момент компиляции, что размер массива сопадает числу элементов ENUMа.
Если используется явное указание индексов
{[x]=y, [z]=w,...}
, это не спасет от пропущенного элемента в середине. А если как в исходном примере{y, w, ...}
, то от перепутанного порядка.datacompboy
28.10.2024 07:33Имеется ввиду любая реализация проверки на число элементов.
Да, как, и писал выше, явные индексы делают эту проверку бесполезной. В остальном случае, проверка достаточна с практической точки зрения для напоминания о месте использования. Типичные случаи добавления нового элемента проблем не доставляют, и пропущенную запятую при первоначальном наполнении ловит.
Dovgaluk
28.10.2024 07:33Тут можно последнюю константу потерять, и будет выход за границу массива.
Стоит ещё добавить размер:
static const char * const s_status_str[MAX_DBG_STATUS] = {
neon1ks
28.10.2024 07:33Этот код хорошо покрывается юнит тестами.
Andrey2008 Автор
28.10.2024 07:33Но не покрыт :)
P.S. Про юнит-тесты в DPDK отдельная земетка скоро будет :)
Arioch
28.10.2024 07:33Тут вам любители Перла привет передают. Строго говоря это не ошибка, на корректность компиляции не влияет. Но вот на чтение людьми и последующие модификации, на GREP'ы и так далее - очень даже.
/* DBG_STATUS_DATA_DID_NOT_TRIGGER */
DBG_STATUS_DATA_DIDNT_TRIGGER,
Хорошо бы такие опечатки тоже ловить.
я нашел это только пропарсив оба файла перлом, загнав имена в словари и распечатав отсортированые ключи. ну и сравнив потом диффом.
CitizenOfDreams
28.10.2024 07:33Вот только найти её, просматривая код, ой как непросто.
Наивный вопрос не-программиста: анахрена так вообще делать, и как потом редактировать эти два отдельных списка имен и строк? Почему не писать что-то вроде:
const char STATUS_OK "Все в порядке" //у нас все получилось const char STATUS_NOT_OK "Все не в порядке" //у нас ничего не получилось const char STATUS_OOPS "Вообще все плохо" //Наташа, мы все уронили
COKPOWEHEU
28.10.2024 07:33Потому что все эти DBG_STATUS_ предназначены в первую очередь не для преобразования в строку, а чтобы отслеживать ошибки внутри программы. Возвращать из функций, использовать в качестве кода завершения программы, возможно для логов.
Идея использовать вместо короткого числа указатель на строку, конечно, интересная, но указатели ведь при каждом запуске новые будут. То есть когда программа упала, расшифровать коды ошибок будет сложно.
Panzerschrek
28.10.2024 07:33Задача преобразования enum в строку решена весьма опасным способом. Лучше было бы написать switch и case под каждое значение. Современные компиляторы достаточно умны и укажут, если какое-то значение пропущено, главное - не ставить default. При этом этот код столь же оптимален, как и вручную заполненная таблица - компиляторы умеют switch в таблицу переходов преобразовывать.
https://godbolt.org/z/Gfo1b7G4e - как видно, что gcc, что clang умеют так оптимизировать код.
DrGluck07
28.10.2024 07:33Хороший способ. Единственное возражение, которое приходит в голову, на микроконтроллерах может потребоваться возможность положить этот массив строк в определенную память.
datacompboy
28.10.2024 07:33удивительно, как странно выглядит код в gcc если добавить -fPIC. clang'овый сразу уже позиционно-независим и не меняется от флага
playermet
28.10.2024 07:33А я недавно ознакомился с вот такой особенностью С++.
// Объявление с именем example char const* example() { return "function"; } // Имя example уже занято, но ошибок компиляции нет // Объявление с именем example в struct namespace struct example { operator char const* () { return "struct"; } }; int main() { char const* s = example(); // Выведет "function" // Но если удалить функцию, выведет "struct" std::cout << s << std::endl; char const* ss = struct example(); // Выведет "struct" std::cout << ss << std::endl; }
Объявление struct, class, union и enum работает так, словно неявно обернуто в typedef. Но только если имя типа еще не занято чем-то другим. Итого, получается потенциал для сложноотслеживаемого бага.
Kelbon
28.10.2024 07:33а можно было убрать массив и написать switch, где компилятор необработанные кейсы сам покажет
Andrey2008 Автор
Движок habr для кода умный там, где не надо :)
Он схлопывает две пустые строчки в одну, поэтому не удаётся показать лишнюю пустую строчку в коде, про которою идёт речь. Она здесь:
Как вариант можно у нас в блоге посмотреть статью, там как показан так, как надо.
sshmakov
А я просто посчитал количество элементов в енуме и строковых констант, на 1 не сошлось.
redfox0
Я искал отсутствующую запятую типа такой, не нашёл: