Как указать размер перечисления в GCC? - PullRequest
8 голосов
/ 02 марта 2012

Я хочу указать для перечисления размер перечисления 64 бита. Как это возможно через GCC? Код не должен быть «переносимым», потому что меня интересует только то, как заставить код работать на компиляции GCC для x86-32 и x86-64 Linux. Это означает, что любой хак, который может предоставить нужную мне функциональность, подойдет, если он работает для этих целей.

Учитывая этот код:

#include <stdlib.h>
#include <stdio.h>

enum some_enum
{
    garbage1,
    garbage2
};

int main(void)
{
    enum some_enum some_val;
    printf("size: %lu\n", sizeof(some_val));

    return EXIT_SUCCESS;
}

В настоящее время выводится 4, тогда как я хочу, чтобы размер был равен 8. Попытка указать значения в присваивании enum, превышающем 4 байта, вызывает предупреждение. Например,

enum some_enum
{
    garbage1 = '12345',
    garbage2
};

Будет производить:

warning: character constant too long for its type [enabled by default]

ответ на подобный вопрос здесь, похоже, не дает хороших результатов. То есть, такое же предупреждение выдается в результате:

enum some_enum
{
    garbage1 = 'adfs',
    garbage2 = 'asdfasdf'
};

Примечание: многосимвольное предупреждение можно отключить путем компиляции с -Wno-multichar.


Обоснование

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

enum mnemonic
{
    mov = 'mov',
    cmp = 'cmp',
    sysenter = 'sysenter'
};

Затем я могу легко хранить семантическую информацию с помощью следующего кода:

enum mnemonic insn;

char *   example_insn = "mov";
uint64_t buf          = 0;

strncpy((char *)&buf, example_insn, sizeof(uint64_t));

Если buf было бы enum mnemonic, то нам больше ничего не нужно делать. strncpy используется для заполнения байтов после конца строки нулевыми символами. Если я не смогу сделать это, мне придется сделать что-то вроде этого:

if(strcmp(example_insn, "mov") == 0) {
    insn = mov;
} else if(strcmp(example_insn, "cmp") == 0) {
    insn = cmp;
} ...

Поскольку эта процедура будет выполняться миллионы раз, такая оптимизация будет иметь огромное значение. Я намерен сделать то же самое для операндов, таких как регистры тоже.

Ответы [ 8 ]

8 голосов
/ 02 марта 2012

Как сказано в ответе Matteo Italia, gcc позволяет вам определить 64-битный тип перечисления, указав 64-битное значение для одного из членов. Например:

enum some_enum {
    /* ... */
    max = 0x7fffffffffffffff
};

Что касается использования 'mov', 'cmp' и т. Д., То нет необходимой корреляции между представлением строкового литерала, например "mov", и представлением многосимвольной символьной константы, такой как 'mov'. .

Последнее допустимо (и поддерживается gcc), но значение определяется реализацией. Стандарт гласит, что типом всегда является int, и gcc, похоже, не имеет расширения, позволяющего вам это переопределить. Так что если int равен 4 байта, тогда 'sysenter', если он вообще принят, не обязательно будет иметь значение, которое вы ищете. Кажется, gcc игнорирует все байты младшего разряда такой константы. Кажется, что значение константы одинаково для систем с прямым и обратным порядком байтов - это означает, что не будет постоянно соответствовать представлению подобного строкового литерала.

Например, эта программа:

#include <stdio.h>
int main(void) {
    const char *s1 = "abcd";
    const char *s2 = "abcdefgh";
    printf("'abcd'     = 0x%x\n", (unsigned)'abcd');
    printf("'abcdefgh' = 0x%x\n", (unsigned)'abcdefgh');
    printf("*(unsigned*)s1 = 0x%x\n", *(unsigned*)s1);
    printf("*(unsigned*)s2 = 0x%x\n", *(unsigned*)s2);
    return 0;
}

производит этот вывод при компиляции с gcc в системе с прямым порядком байтов (x86):

'abcd'     = 0x61626364
'abcdefgh' = 0x65666768
*(unsigned*)s1 = 0x64636261
*(unsigned*)s2 = 0x64636261

и этот вывод в системе с прямым порядком байтов (SPARC):

'abcd'     = 0x61626364
'abcdefgh' = 0x65666768
*(unsigned*)s1 = 0x61626364
*(unsigned*)s2 = 0x61626364

Так что, боюсь, ваша идея сопоставления символьных констант, таких как 'mov', со строками, такими как "mov", не сработает. (Возможно, вы могли бы нормализовать строковые представления в порядке с прямым порядком байтов, но я бы не стал использовать этот подход сам.)

Проблема, которую вы пытаетесь решить, - это быстрое сопоставление строк, таких как "mov", с конкретными целочисленными значениями, которые представляют инструкции процессора. Вы правы, что длинная последовательность вызовов strcmp() будет неэффективной (вы действительно измерили ее и обнаружили, что скорость недопустима?), Но есть более эффективные способы. Хэш-таблица в некотором роде, вероятно, лучшая. Существуют инструменты для генерации совершенных хеш-функций, так что относительно дешевое вычисление значения строки дает уникальное целочисленное значение.

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

Это предполагает, что перечисление - лучший подход здесь; это не может быть. Если бы я делал это, центральная структура данных была бы коллекцией структур, каждая из которых содержит строковое имя оператора и любую другую информацию, связанную с ним. Хеш-функция отображает строки типа "mov" на индексы в этой коллекции. (Я намеренно размышляю о том, какую «коллекцию» использовать; с правильной хеш-функцией это может быть простой массив.) С таким решением я не думаю, что нужен 64-битный тип enum .

6 голосов
/ 02 марта 2012

Вы можете использовать union тип:

union some {
    enum { garbage1, garbage2 } a;
    int64_t dummy;
};
4 голосов
/ 02 марта 2012

Вы неверно истолковали предупреждение, оно говорит о том, что символьные литералы всегда имеют тип int, никогда не имеют тип long или long long.

Вы можете сойти с рук что-то вроде этого:

enum foo {
    garbage1 = (long long)'1' << 32 | (long long)'2' << 24 | (long long)'3' << 16 | (long long)'4' << 8 | (long long)'5',
    garbage2
};

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

4 голосов
/ 02 марта 2012

Хотя стандарт C99 определяет, что перечисление не может основываться ни на чем, кроме int (§6.7.2.2 ¶2) 1 , похоже, что gcc следуетидея C ++, что если значение в enum больше, чем int, оно может основываться на большем целочисленном типе.У меня нет проблем с этим кодом, ни на x86, ни на x64:

enum myEnum
{
    a=1234567891234567890LL
};

int main()
{
    enum myEnum e;
    printf("%u %u", sizeof(void *), sizeof(e));
    return 0;
}

на x86 Я получаю

4 8

и на x64 (на моей машине) Я получаю

8 8

Хотя, прося педантичного уважения к стандарту, я получаю, как и ожидалось:

matteo@teodeb:~/cpp$ gcc -ansi -pedantic testenum.c
testenum.c:5:7: warning: use of C99 long long integer constant
testenum.c:5: warning: ISO C restricts enumerator values to range of ‘int’
  1. На самом деле, это немного сложнее;Specif4 указывает, что реализация может свободно выбирать в качестве «базового типа» любой конкретный тип, «совместимый с char, целочисленный тип со знаком или целочисленный тип без знака», при условии, что он может представлять все элементы enum.

    С другой стороны, ¶2 указывает, что каждый член enum должен быть представлен как int, поэтому, даже если реализация свободна основывать enum даже наgazillion bit integer, константы, определенные для него, не могут быть ничем, что не может быть представлено int.Таким образом, это означает, что на практике компилятор не будет основывать enum на чем-то большем, чем int, но может основывать его на чем-то меньшем, если ваши значения нене требуется полный диапазон int.

Спасибо @ jons34yp за указание на мою первоначальную ошибку.

1 голос
/ 13 июня 2013

Просто чтобы ответить на оригинальный вопрос в заголовке - C ++ 11 позволяет вам указать тип перечисления и, следовательно, его размер:

enum class mynamedenum : long {
  FOO,
  BAR
}
1 голос
/ 02 марта 2012

Пер Йоханссон ударил по голове своим ответом здесь . В качестве конкретного примера того, как использовать эту технику, я написал эту программу (insn_enum.c):

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

enum insn {
    /*
     * Have the characters backwards because C treats the value as an
     * integer (of size 64  bits in this case). There is no need for
     * a null terminator since we are treating the values as an integer,
     * not a string.
     */
    sysenter = (uint64_t)'r' << 56 | (uint64_t)'e' << 48 |
            (uint64_t)'t' << 40 | (uint64_t)'n' << 32 |
            (uint64_t)'e' << 24 | (uint64_t)'s' << 16 |
            (uint64_t)'y' << 8 | (uint64_t)'s',
};

int main(void)
{
    enum insn some_insn = sysenter;
    char * insn = "sysenter";

    uint64_t val = 0;

    /*
     * We can optimise this by traversing backwards (little endian) setting
     * 0 till a NULL char is found, although I will not bother implementing
     * this till I have done some profiling.
     */
    strncpy((char * )&val, insn, sizeof(uint64_t));

    printf("size: %" PRIuPTR"\n", sizeof(enum insn));

    if(some_insn == val) {
        puts("Works");
    } else {
        puts("Doesn't work");
    }

    return EXIT_SUCCESS;
}

Это может быть скомпилировано со следующим makefile:

all:
    gcc -std=gnu99 -m32 -Wall insn_enum.c -o insn_enum_32
    gcc -std=gnu99 -m64 -Wall insn_enum.c -o insn_enum_64

clean:
    rm -f insn_enum_32
    rm -f insn_enum_64

При запуске с ./insn_enum_32 && ./insn_enum_64 будет напечатано:

size: 8
Works
size: 8
Works

Следует отметить, что это только показывает, что мы можем заставить этот трюк работать на x86-32 и x86-64 (единственные две платформы, на которые я собираюсь ориентироваться). На самом деле этот трюк гарантированно не работает в системах с прямым порядком байтов из-за того, что язык обрабатывает enum как целочисленное значение. Также я не уверен, что мы можем гарантировать, что компилятор обязательно будет использовать uint64_t в качестве размера enum, даже если мы укажем его так, как мы. Действительно, компиляция с -pedantic выдаст предупреждение:

gcc -std=gnu99 -m32 -pedantic -Wall insn_enum.c -o insn_enum_32
insn_enum.c:13:13: warning: ISO C restricts enumerator values to range of ‘int’
gcc -std=gnu99 -m64 -pedantic -Wall insn_enum.c -o insn_enum_64
insn_enum.c:13:13: warning: ISO C restricts enumerator values to range of ‘int’
0 голосов
/ 02 марта 2012

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

Пример программы gen-instructions может выглядеть следующим образом

#include <inttypes.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    for(int i = 1; i < argc; ++i)
    {
        uint64_t value;
        strncpy((char *)&value, argv[i], sizeof value);
        printf("#define %s 0x%.16" PRIX64 "\n", argv[i], value);
    }

    return 0;
}

с соответствующим правилом make-файла

instructions.h : instructions.list gen-instructions
    ./gen-instructions `cat $<` > $@
0 голосов
/ 02 марта 2012

Perhas, который вы могли бы использовать, определяет?

#define GARBAGE1 12345L
#define GARBAGE2 67890L

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

Возможно, попробуйте:

enum
{
 garbage1,
 garbage2,
 sentinel = 12345L
}

И видите?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...