Можем ли мы использовать va_arg с профсоюзами? - PullRequest
7 голосов
/ 14 сентября 2011

6.7.2.1 В параграфе 14 моего проекта стандарта C99 сказано следующее о соединениях и указателях (выделение, как всегда, добавлено):

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

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

union ints { int i; unsigned u; };

int i = 4;
union ints is = *(union ints *)&i;
int j = is.i; // legal
unsigned k = is.u; // not so much

7.15.1.1 параграф 2 имеет следующее:

Макрос va_arg расширяется до выражения, которое имеет указанный тип и значение следующего аргумента ввызов.Параметр ap должен быть инициализирован макросом va_start или va_copy (без промежуточного вызова макроса va_end для sameap).Каждый вызов макроса va_arg изменяет ap, так что значения последовательных аргументов возвращаются по очереди.Параметр type должен быть именем типа, указанным так, чтобы тип указателя на объект, имеющий указанный тип, можно было получить простым постфиксом от * до type.Если фактического следующего аргумента нет, или если тип не совместим с типом фактического следующего аргумента (как продвигается в соответствии с продвижением аргумента по умолчанию), поведение не определено, за исключением следующих случаев:

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

- один тип являетсяуказатель на void, а другой указатель на тип символа.

Я не собираюсь приводить часть о продвижении аргументов по умолчанию.Мой вопрос: это определенное поведение:

void func(int i, ...)
{
    va_list arg;
    va_start(arg, i);
    union ints is = va_arg(arg, union ints);
    va_end(arg);
}

int main(void)
{
    func(0, 1);
    return 0;
}

Если это так, то кажется, что преодоление требования "и значение совместимо с обоими типами" преобразования целых чисел со знаком (без знака) (это совместимо с обоими типами) (хотя это довольно сложно сделать с юридической точки зрения).Если нет, то было бы безопасно использовать unsigned в этом случае, но что, если в union было больше элементов с более несовместимыми типами?Если мы можем гарантировать, что мы не получим доступ к объединению по элементу (т.е. мы просто скопируем его в другой union или пространство хранения, которое мы рассматриваем как union), и что все элементы объединения имеют одинаковый размерэто разрешено с varargs?Или это будет разрешено только с указателями?

На практике я ожидаю, что этот код почти никогда не потерпит неудачу, но я хочу знать, определено ли его поведение.Мое текущее предположение состоит в том, что это, кажется, не определено, но кажется невероятно глупым.

Ответы [ 3 ]

3 голосов
/ 14 сентября 2011

У вас есть пара отключенных вещей.

Указатель на объект объединения, соответствующим образом преобразованный, указывает на каждый из его элементов (или, если элемент является битовым полем, то на блокв котором он находится), и наоборот.

Это не означает, что типы совместимы.На самом деле они не совместимы.Поэтому следующий код неверен:

func(0, 1); // undefined behavior

Если вы хотите передать объединение,

func(0, (union ints){ .u = BLAH });

Вы можете проверить, написав код,

union ints x;
x = 1;

GCC выдает сообщение «error: несовместимые типы в присваивании» при компиляции.

Однако большинство реализаций «вероятно» будут делать правильные вещи в обоих случаях.Существуют и другие проблемы ...

union ints {
    int i;
    unsigned u;
};

int i = 4;
union ints is = *(union ints *)&i; // Invalid
int j = is.i; // legal
unsigned k = is.u; // also legal (see note)

Поведение при разыменовании адреса типа, использующего тип, отличный от фактического типа *(uinon ints *)&i, иногда не определено (при поиске ссылки, но яЯ почти уверен в этом).Однако в C99 разрешен доступ к члену объединения, отличному от самого последнего сохраненного члена объединения (или это C1x?), Но значение определяется реализацией и может быть представлением прерывания.

По поводу прокалывания типов через объединения: Как отмечает Паскаль Куок, именно TC3 определяет поведение доступа к элементу объединения, отличному от самого последнего сохраненного элемента.TC3 - это третье обновление C99.Хорошая новость заключается в том, что эта часть TC3 действительно кодифицирует существующую практику - так что думайте о ней как о фактической части C до TC3.

2 голосов
/ 14 сентября 2011

Поскольку стандарт гласит:

Тип параметра должен быть именем типа, указанным таким образом, чтобы тип указателя на объект, имеющий указанный тип, можно было получить простым постфиксом * ктип.

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

Если выобмануть и попытаться передать простой int или unsigned int вместо объединения, тогда вы будете вызывать неопределенное поведение.Таким образом, вы можете использовать:

union ints u1 = ...;

func(0, (union ints) { .i = 0 });
func(1, (union ints) { .u = UINT_MAX });
func(2, u1);

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

func(1, 0);

Аргументы не являются типами объединения.

0 голосов
/ 14 сентября 2011

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

Возвращаясь к вашему первому фрагменту кода, у него тоже есть проблема:

union ints is = *(union ints *)&i;

Это нарушение псевдонимов и вызывает неопределенное поведение. Вы можете избежать этого, используя memcpy, и я полагаю, тогда это будет законно ..

Я также немного смущен вашим комментарием здесь:

unsigned k = is.u; // not so much

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

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

...