Случай переключения C / C ++ на массивах символов - PullRequest
6 голосов
/ 16 августа 2011

У меня есть несколько структур данных, каждая из которых имеет поле 4 байта.

Поскольку на моей платформе 4 байта равны 1 int, я хочу использовать их в метках case:

switch (* ((int*) &structure->id)) {
   case (* ((int*) "sqrt")): printf("its a sqrt!"); break;
   case (* ((int*) "log2")): printf("its a log2!"); break;
   case (((int) 'A')<<8 + (int) 'B'): printf("works somehow, but unreadable"); break;
   default: printf("unknown id");
}

Это приводит к ошибке компиляции, сообщая, что выражение case не уменьшается до int.

Как я могу использовать массивы символов ограниченного размера и преобразовывать их в числовые типы для использования в switch / case?

Ответы [ 7 ]

4 голосов
/ 17 августа 2011

Следуйте точному методу, используемому при кодировании видео с кодами FourCC:

Установить значение FourCC в C ++

#define FOURCC(a,b,c,d) ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) )

Вероятно, хорошая идея использовать перечисляемые типы или макросы для каждого идентификатора:

enum {
    ID_SQRT = FOURCC( 's', 'q', 'r', 't'),
    ID_LOG2 = FOURCC( 'l', 'o', 'g', '2')
};

int structure_id = FOURCC( structure->id[0], 
                           structure->id[1],
                           structure->id[2],
                           structure->id[3] );
switch (structure_id) {
case ID_SQRT: ...
case ID_LOG2: ...
}
2 голосов
/ 17 августа 2011

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


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

~/$ cat main.cc

#include <iostream>

#ifdef I_AM_CERTAIN_THAT_MY_PLATFORM_SUPPORTS_THIS_CRAP
int main () {
    const char *foo = "fooo";
    switch ((foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | (foo[3]<<0)) {
    case 'fooo': std::cout << "fooo!\n";  break;
    default:     std::cout << "bwaah!\n"; break;
    };
}
#else
#error oh oh oh
#endif

~/$ g++ -Wall -Wextra main.cc  &&  ./a.out
main.cc:5:10: warning: multi-character character constant
fooo!

edit: О, смотрите, прямо под отрывком из грамматики есть 2.13.2 Литералы символов , Bullet 1 :

[...] Обычный символьный литерал, содержащий более одного символа c-char, является литералом с несколькими символами. Мультяшный Литерал ter имеет тип int и значение, определяемое реализацией.

Но во второй пули:

[...] Значение литерала широких символов, содержащего несколько символов c, определяется реализацией.

Так что будьте осторожны.

2 голосов
/ 17 августа 2011

Проблема в том, что case ветви switch ожидают постоянного значения. В частности, константа, известная во время компиляции 1004 *. Адрес строк не известен во время компиляции - компоновщик знает адрес, но даже не конечный адрес. Я думаю, что окончательный, перемещенный адрес доступен только во время выполнения.

Вы можете упростить вашу задачу до

void f() {
    int x[*(int*)"x"];
}

Это приводит к той же ошибке, поскольку адрес литерала "x" не известен во время компиляции. Это отличается от, например,

void f() {
    int x[sizeof("x")];
}

Поскольку компилятор знает размер указателя (4 байта в 32-битных сборках).

Теперь, как исправить вашу проблему? Две вещи приходят мне на ум:

  1. Не делайте поле id строкой, а целым числом, а затем используйте список констант в ваших case выражениях.

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

Вот эскиз кода, иллюстрирующий вторую идею:

struct MyStructure {
   const char *id;
   void (*printType)(struct MyStructure *, void);
   void (*doThat)(struct MyStructure *, int arg, int arg);
   /* ... */
};

static void printSqrtType( struct MyStructure * ) {
   printf( "its a sqrt\n" );
}

static void printLog2Type( struct MyStructure * ) {
   printf( "its a log2\n" );
}

static void printLog2Type( struct MyStructure * ) {
   printf( "works somehow, but unreadable\n" );
}

/* Initializes the function pointers in the structure depending on the id. */
void setupVTable( struct MyStructure *s ) {
  if ( !strcmp( s->id, "sqrt" ) ) {
    s->printType = printSqrtType;
  } else if ( !strcmp( s->id, "log2" ) ) {
    s->printType = printLog2Type;
  } else {
    s->printType = printUnreadableType;
  }
}

С этим, ваш оригинальный код может просто сделать:

void f( struct MyStruct *s ) {
    s->printType( s );
}

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

2 голосов
/ 17 августа 2011

Я считаю, что проблема здесь в том, что в C каждая case метка в выражении switch должна быть целочисленным константным выражением. Из спецификации C ISO, раздел 6.8.4.2/3:

Выражение каждой метки регистра должно быть целочисленным константным выражением [...]

(мой акцент)

Спецификация C затем определяет «целочисленное константное выражение» как константное выражение, где (& sect; 6.6 / 6):

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

(опять мой акцент). Это говорит о том, что вы не можете типизировать символьный литерал (указатель) к целому числу в операторе case, так как это приведение не допускается в выражении с целочисленной константой.

Интуитивно понятно, что причиной этого может быть то, что в некоторых реализациях фактическое расположение строк в сгенерированном исполняемом файле не обязательно указывается до связывания. Следовательно, компилятор может не иметь возможности генерировать очень хороший код для оператора switch, если метки зависят от константного выражения, которое косвенно зависит от адреса этих строк, поскольку, например, он может упустить возможности для компиляции таблиц переходов. Это всего лишь пример, но более строгий язык спецификации явно запрещает вам делать то, что вы описали выше.

Надеюсь, это поможет!

1 голос
/ 17 августа 2011

Я только что использовал этот макрос, как в случае № 3 в вопросе или ответе Френеля.

#define CHAR4_TO_INT32(a, b, c, d) ((((int32_t)a)<<24)+ (((int32_t)b)<<16) + (((int32_t)c)<<8)+ (((int32_t)d)<<0)) 

switch (* ((int*) &structure->id)) {
   case (CHAR4_TO_INT32('S','Q','R','T')): printf("its a sqrt!"); break;
}
1 голос
/ 17 августа 2011

Это особенно опасно из-за выравнивания: на многих архитектурах int выровнено по 4 байта, а массивы символов - нет. Например, в sparc, даже если этот код может скомпилироваться (чего не может быть, потому что адрес строки неизвестен до времени ссылки), он немедленно вызовет SIGBUS.

0 голосов
/ 16 августа 2011

это больше, чем С ++.

union int_char4 {int_32 x; char [4] y;}

объединение объявляет, определяет, что его члены должны начинаться с одного и того же адреса, по сути, предоставляя разные типы для одного и того же набора байтов.

int_char4 ic4; ic4.x - это int, а ic4.y - указатель на первый байт массива char.

так как, вы хотите узнать, реализация зависит от вас.

...