Обеспечение ширины значений перечисления в структуре в C99

У меня есть структура, предназначенная для использования 32-битного хранилища:

struct foo_t {
    color_t color : 10
    some_type_t some_field : 22;
} 

, где color_t — это перечисление, определенное как

typedef enum {
    RED = 0,
    // other values...
    BLUE = 255
} color_t

Обратите внимание, что значения color_t в настоящее время умещаются в 8 бит, хотя в будущем мы можем добавить больше значений (таким образом, мы зарезервировали 10 бит для color).

В C99 мне интересно, есть ли гарантия того, что компилятор будет соблюдать ширину color. Как обсуждалось в этом вопросе, компилятор может выбрать для представления color_t в виде символа. В этот момент указанная ширина кажется неверной в соответствии с Спецификация C99:

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

Как я могу заставить поле color использовать 10 бит? Обратите внимание, что проблема исчезает, если компилятор использует обычное целое число для представления color_t, но такое поведение нельзя предполагать.


person user3120046    schedule 24.06.2014    source источник
comment
компилятор может выбрать представление значений color_t в виде символов Это звучит неправильно.   -  person this    schedule 25.06.2014
comment
@это можно уточнить?   -  person ouah    schedule 25.06.2014
comment
@ouah enum — это целое число.   -  person this    schedule 25.06.2014
comment
@ это, конечно, не так, enum константы - это int, enum типы не обязательно должны быть int.   -  person ouah    schedule 25.06.2014
comment
@user3120046 user3120046 с использованием типа, отличного от перечисления (например, unsigned int), поскольку тип члена color был бы более переносимым решением IMHO   -  person ouah    schedule 25.06.2014
comment
@ user3120046: Но есть ли причина не делать это int?   -  person mafso    schedule 25.06.2014


Ответы (3)


Добавьте последний тег к вашему определению enum:

typedef enum {
    //...
    DUMMY_FORCE_WIDTH = 0xffffffffu, // INT_MAX,
} color_t;

Это имеет дополнительное преимущество, заставляющее компилятор / ABI предоставлять вашему enum достаточно места для роста повсюду.

Конечно, это предполагает, что ваш компилятор допускает enum в качестве типов битовых полей. В этом нет необходимости, хотя он должен диагностировать это как нарушение ограничения, чем:

6.7.2.1 Спецификаторы структуры и объединения §5 (ограничения)

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

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

person Deduplicator    schedule 24.06.2014
comment
Я думал об этом (и я должен был включить это в вопрос), но тогда у нас есть это фиктивное значение, которое сбивает с толку, если мы предположим, что color_t является частью общедоступного API (что применимо к моей проблеме). Вам нужно будет задокументировать, что FORCE_WIDTH нельзя использовать в качестве допустимого значения для перечисления и т. д. - person user3120046; 25.06.2014
comment
На самом деле имени вообще не должно быть в документации по API. Если FORCE_WIDTH недостаточно описателен для вас, как насчет того, чтобы еще раз добавить фиктивный элемент, чтобы сказать, что это недопустимое значение? Во всяком случае, такое использование члена для форсирования размера довольно хорошо зарекомендовало себя. - person Deduplicator; 25.06.2014
comment
Я принял ваш ответ, потому что он подразумевает минимальные изменения реализации, но вы правы в том, что сочетание типа enum и объявления битового поля не является стандартным C99 для начала. Что я не очень хорошо понимаю, так это то, что спецификация C99 исключает битовые поля типа uint16_t или uint64_t? - person user3120046; 25.06.2014
comment
Нет, в нем говорится, что реализация может разрешить их с указанной семантикой и в противном случае должна диагностировать их как ошибку. (см. цитату выше) - person Deduplicator; 25.06.2014
comment
Таким образом, использование битового поля uint16_t непереносимо. Интересно. Спасибо! - person user3120046; 25.06.2014

Строго говоря, язык C (C99) гарантирует поддержку только типов int, signed int, unsigned int и _Bool в объявлениях битовых полей, хотя конкретным реализациям явно разрешено поддерживать другие типы как зависящее от реализации поведение.

Другими словами, в этом случае мы имеем дело с поведением, зависящим от реализации, поскольку вы объявили свои битовые поля с типами enum. Лучшим подходом было бы обратиться к документации вашего компилятора, но я ожидаю, что он либо удовлетворит ваш запрос на 10 бит, либо сгенерирует диагностическое сообщение, если выбранная единица хранения не способна вместить 10 бит.

В своей практике я обычно использую следующий подход

typedef enum color_t {
  RED = 0,
  // other values...
  BLUE,
  COLOR_COUNT
} color_t;

#define COLOR_BIT_WIDTH 8u

STATIC_ASSERT(COLOR_COUNT <= (1 << COLOR_BIT_WIDTH));
// Use your favorite implementation of `STATIC_ASSERT`

struct foo_t {
  unsigned color : COLOR_BIT_WIDTH;
  ...
};

Обратите внимание, что эта реализация не пытается зарезервировать дополнительные биты для будущего расширения типа цвета (у вас действительно есть причина для этого?). Он также не пытается автоматически вычислить ширину битового поля. Вместо этого он просто ловит ситуацию, когда ширины уже недостаточно, тем самым вынуждая сопровождающего кода замечать проблему и исправлять ее вручную.

P.S. Можно добавить дополнительную простую проверку STATIC_ASSERT, чтобы убедиться, что ширина битового поля не слишком велика, если вы хотите, чтобы она была "правильной".

person AnT    schedule 24.06.2014
comment
Если возможно, я бы хотел, чтобы решение было в рамках стандарта, чтобы я не зависел от конкретного компилятора. В его нынешнем виде код может работать в каком-то компиляторе, но он определенно (как вы указываете) не переносим. - person user3120046; 25.06.2014
comment
Это может привести к тому, что сопровождающий заметит наличие проблемы, но не обязательно выделит все места, где нарушенные предположения были жестко закодированы. - person Deduplicator; 25.06.2014
comment
@Deduplicator: Идея состоит в том, чтобы писать код, используя COLOR_BIT_WIDTH везде, где требуется битовая ширина. Это полностью покрывает вопрос битовой ширины. Если кто-то забывает об этом и вместо этого полагается на 8, это действительно проблема, но не особо заметная. Если вы говорите о каком-то другом нарушенном предположении, вам нужно быть более конкретным. - person AnT; 25.06.2014

Вы можете объединить все эти данные в фиксированный 32-битный int (uint32_t), определенный в stdint.h, и получить доступ к значениям с помощью побитовых операторов.

Пример:

uint32_t data = (uint32_t)color<<22 | (uint32_t)some_field;
color=(color_t ) (data>>22);
some_field =(some_type_t ) (data & 0x3FFFFF);
person ETFovac    schedule 24.06.2014
comment
Тогда я больше не могу устанавливать/получать значения полей без использования функций (которые инкапсулируют побитовые операции). Если возможно, я хотел бы сохранить foo_t как структуру и полагаться на компилятор для записи/чтения необходимых битов. - person user3120046; 25.06.2014
comment
@user3120046 user3120046, ты говоришь, что это плохо. Я думаю, что иметь функции, скрывающие побитовые операторы, лучше, чем использовать структуру с битовыми полями и/или использовать побитовые операторы по всему коду. - person R Sahu; 25.06.2014
comment
Я полагаю, что color(myfoo) и myfoo.color - это цена надежного и переносимого представления. Возможно, есть какой-то способ заставить компилятор принимать фиксированную ширину, но если вы его не найдете, это тоже может быть полезно. Другое решение, которое я знаю, состоит в том, чтобы взять первый тип, который подходит (uint16_t для цвета и uint32_t для somefiled), но это уничтожит эффективность использования памяти, которую вы пытались получить. Вы можете зарезервировать эффективность для передачи по сети и сохранения файлов с помощью моего способа , и простота доступа с моим другим способом, если вы объедините эти два подхода и просто преобразуете их между двумя, когда это необходимо. - person ETFovac; 25.06.2014
comment
@RSahu: это не страшно :-) но если вы можете решить проблему внутри языка, это кажется правильным решением: поскольку битовые поля разрешены в структурах, зачем использовать функцию? Если вы посмотрите на ответ дедупликатора, дизайн структуры останется прежним, так что это относительно чистое изменение. - person user3120046; 25.06.2014