#c #string #macros #switch-statement
#c #строка #макросы #switch-инструкция
Вопрос:
Мне нужно переключаться на основе 4-символьной строки. Я помещаю строку в объединение, чтобы я мог, по крайней мере, ссылаться на нее как на 32-разрядное целое число.
union
{
int32u integer;
char string[4];
}software_version;
Но теперь я не знаю, что писать в операторах case. Мне нужен какой-нибудь макрос для преобразования 4-символьного строкового литерала в целое число. Например,
#define STRING_TO_INTEGER(s) ?? What goes here ??
#define VERSION_2_3_7 STRING_TO_INTEGER("0237")
#define VERSION_2_4_1 STRING_TO_INTEGER("0241")
switch (array[i].software_version.integer)
{
case VERSION_2_3_7:
break;
case VERSION_2_4_1:
break;
}
Есть ли способ создать макрос STRING_TO_INTEGER(). Или есть лучший способ справиться с переключением?
Комментарии:
1. Вы знаете, что это невозможно перенести через границы конечного байта? Возможно, было бы лучшей идеей просто определить целое число версии, которое увеличивается или вычисляется из числовой версии при запуске программы.
2. Я не думаю, что вы можете перейти из string -> number во время компиляции.
3. Почему вы настаиваете на преобразовании ваших
VERSION
констант из строк с помощью макроса? Вы могли бы просто… вы знаете… помещайте целые числа напрямую (например,#define VERSION_2_3_7 237
)…4. @SethCarnegie Можно использовать
sscanf
для разбора целых чисел внутри строк.5. Также обратите внимание, что если вы придерживаетесь представления version в виде массива символов и вам нужно 4 символа, вы должны определить строковый элемент структуры так, чтобы он принимал 5 символов, один для NULL.
Ответ №1:
Переносимый пример кода:
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#define CHARS_TO_U32(c1, c2, c3, c4) (((uint32_t)(uint8_t)(c1) |
(uint32_t)(uint8_t)(c2) << 8 | (uint32_t)(uint8_t)(c3) << 16 |
(uint32_t)(uint8_t)(c4) << 24))
static inline uint32_t string_to_u32(const char *string)
{
assert(strlen(string) >= 4);
return CHARS_TO_U32(string[0], string[1], string[2], string[3]);
}
#define VERSION_2_3_7 CHARS_TO_U32('0', '2', '3', '7')
#define VERSION_2_4_1 CHARS_TO_U32('0', '2', '4', '1')
int main(int argc, char *argv[])
{
assert(argc == 2);
switch(string_to_u32(argv[1]))
{
case VERSION_2_3_7:
case VERSION_2_4_1:
puts("supported version");
return 0;
default:
puts("unsupported version");
return 1;
}
}
Код предполагает только существование целочисленных типов uint8_t
и uint32_t
и не зависит от ширины и знаковости типа char
, а также от порядкового номера. Она свободна от коллизий, пока кодировка символов использует только значения в диапазоне uint8_t
.
Ответ №2:
Вы включаете четырехсимвольные коды следующим образом
switch (fourcc) {
case 'FROB':
}
Обратите внимание на разницу: "XXXX"
это строка, 'XXXX'
это символьный / целочисленный литерал.
Однако я бы предложил вам вместо этого использовать отдельные номера версий, например:
struct Version {
int major, minor, patch;
};
bool smaller (Version lhs, Version rhs) {
if (lhs.major < rhs.major) return true;
if (lhs.major > rhs.major) return false;
if (lhs.minor < rhs.minor) return true;
if (lhs.minor > rhs.minor) return false;
if (lhs.patch < rhs.patch) return true;
if (lhs.patch > rhs.patch) return false; // redundant, for readabiltiy
return false; // equal
}
Комментарии:
1. вы неправильно интерпретируете стандарт. Доступ к другому элементу, отличному от того, в который был записан последним, возможен только в том случае, если значение является представлением-ловушкой для другого типа.
int32u
(Я полагаю, это то же самое, чтоuint32_t
) не имеет представлений trap.2. Я не понимаю, как это помогает моему оператору switch.
3. @Rocketmagnet: Вы можете переключать только целочисленные типы, переключение на строки / массивы символов не определено.
4. @Jens: Я удалю эту фразу. Спасибо за подсказку.
5. @phresnel Да, я знаю это, как очевидно из моего вопроса. Вот почему я хочу автоматический способ преобразования строки в целое число.
Ответ №3:
Обновлено:
#define VERSION_2_3_7 '0237'
Комментарии:
1. Ему нужно перейти от строки к числу, а не от числа к строке.
2. Вопрос в обратном направлении.
3. @SethCarnegie, смотри комментарий вверху
4. Люди, прекратите голосовать против этого. На самом деле это правильный ответ.
5. @Rocketmagnet, этот ответ некорректен, не используйте его таким образом. Символьные константы, которые содержат более одного символа, как предложенные
'0237'
, зависят от компилятора. И, кроме того, у вас нет причин использовать такой cruft, если вас действительно устраивают только отдельные определения. Просто определите ее как длинное десятичное число без знака, например237UL
.
Ответ №4:
int versionstring_to_int(char * str)
{
char temp [ 1 sizeof software_version.string ]; /* typically 1 4 */
if (!str || ! *str) return -1;
memcpy (temp, str, sizeof temp -1);
temp [sizeof temp -1] = 0;
return atoi (temp );
}
Редактировать:
#define STRING_TO_INTEGER(s) versionstring_to_int(s)
#define VERSION_2_3_7 237
#define VERSION_2_4_1 241
switch ( STRING_TO_INTEGER( array[i].software_version.string) )
{
case VERSION_2_3_7:
break;
case VERSION_2_4_1:
break;
}
Комментарии:
1. Нет, это не работает, потому что это нельзя использовать как в инструкции case.
2. Да, это возможно, просто используйте числовые идентификаторы в падежах. Предполагается, что они являются литералами и тщательно сопоставляются программистом с соответствующим блоком кода. Зачем кому-то нужна строка в качестве метки регистра, когда число так же читаемо и менее подвержено ошибкам?
3. Что, если строка содержала буквы? Например, «ABC1» -> 0x41424331
4. Ну, вам пришлось бы ее разобрать, не так ли? И, похоже, не имеет особого смысла менять схему именования / нумерации в период кризиса среднего возраста проекта. Схемы, подобные этим, должны быть стабильными.
5. суть в том, чтобы переинтерпретировать 4×8 бит строки как 32-разрядное целое число. Не разбирать строку. Любая 4-символьная строка, включая «0_?Q», может быть повторно представлена как целое число. Однако только числовые строки могут быть преобразованы в целое число.