Сообщить о предупреждении при применении макроса container_of к встроенному массиву символов

Когда я применяю макрос container_of к структуре C, содержащей массив символов, я получаю предупреждение: инициализация из несовместимого типа указателя.

Вот коды:

#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})


struct st {
    int a;
    char b;
    char c[16];
    void *p;
};

int main(void)
{
    struct st t = {
        .a = 101,
        .b = 'B',
        .c = "hello",
        .p = NULL
    };

    char (*p)[16] = &t.c;
    struct st *s = container_of(p, struct st, c);

    return 0;
}

Кажется, что тип __mptr - это [], который выводится typeof(). Однако сам ptr имеет тип (*)[]. Очевидно, что они не одинаковы.

Кроме того, если я скомпилирую этот код с помощью clang, все будет в порядке. GCC, кажется, имеет более строгое правило для проверки типов.

Q: Как исправить это предупреждение?


person Douglas Su    schedule 10.10.2016    source источник
comment
Ваше сообщение об ошибке должно включать фактические типы. И (*)[] не является типом, и [] тоже.   -  person too honest for this site    schedule 10.10.2016
comment
спасибо за ваш ответ, здесь [] обозначает тип массива, а (*)[] обозначает указатель на массив.   -  person Douglas Su    schedule 10.10.2016
comment
Мне трудно поверить, что clang принимает этот код. Как минимум, для этого он полагается на расширение. Макрос расширяется до блока кода в скобках, и это не выражение — он вообще не представляет значение.   -  person John Bollinger    schedule 10.10.2016
comment
@JohnBollinger Да, он использует два расширения GCC. Я полагаю, что это определение макроса исходит из ядра Linux (по крайней мере, в настоящее время оно используется в ядре Linux). Эквивалентный макрос в заголовках Windows NT — CONTAINING_RECORD, который, конечно, не использует никаких расширений GCC.   -  person Ian Abbott    schedule 10.10.2016
comment
Макрос container_of из ядра Linux несколько несовершенен, но если вам нужно его использовать, и вы знаете, что элемент является массивом, вы можете использовать элемент массива вместо самого массива. т.е. char *p = &t.c[0]; struct st *s = container_of(p, struct st, c[0]);   -  person Ian Abbott    schedule 10.10.2016
comment
Интересно, что макрос container_of ядра Linux практически не изменился с тех пор, как он впервые появился (в include/linux/kernel.h) в версии ядра Linux 2.5.28.   -  person Ian Abbott    schedule 10.10.2016


Ответы (3)


Объявление __mptr бесполезно, так как оно просто преобразуется в char* в следующей строке. Просто замените макрос на:

#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))

Примечание. GCC 6.2.0 не выдавал никаких предупреждений об исходном коде, за исключением неиспользуемой переменной s.

person André Sassi    schedule 10.10.2016

Тип массива сам по себе не является const, constness на самом деле происходит от того, что каждый отдельный член является const. На самом деле нет синтаксиса, позволяющего объявить, что сам массив const. Вы можете прочитать это сообщение для получения дополнительной информации.

Однако кажется, что макрорасширение typeof в GCC ошибочно: если typeof разрешается в тип массива, квалификатор const применяется к массиву, а не к его отдельным членам. (Андре Сасси отметил, что эта проблема, по-видимому, решена в более новой версии GCC.)

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

const struct st *ct = &t;
typeof(ct->c) *p = &ct->c;
struct st *s = container_of(p, struct st, c);
person jxh    schedule 10.10.2016
comment
Если const является единственной проблемой, то простое удаление должно быть жизнеспособным вариантом, поскольку constness сразу же отбрасывается. Единственная причина, которую я могу придумать, это обработка случая, когда аргумент макроса ptr является указателем на версию с указанием const указанного типа, но даже в этом случае предложение Андре Сасси кажется более подходящим. - person John Bollinger; 10.10.2016
comment
@JohnBollinger: мое предложение применимо только в том случае, если изменение определения макроса невозможно. - person jxh; 10.10.2016

Если вы знаете, что член, передаваемый в container_of, является массивом, вы можете вместо этого передать элемент этого массива, чтобы избежать предупреждения:

char *p = &t.c[0]; /* or: char *p = t->c; */
struct st *s = container_of(p, struct st, c[0]);

Другой способ избежать предупреждения — сделать указатель указателем на void:

void *p = &t.c;
struct st *s = container_of(p, struct st, c);

Что касается исходного кода, похоже, поведение GCC изменилось где-то между GCC 4.9 и GCC 5.4.1. Более поздняя версия GCC не выдает предупреждение «несовместимый тип указателя» для исходного кода. Однако при включении -Wpedantic в более поздней версии GCC появляется предупреждение «Указатели на массивы с разными квалификаторами несовместимы в ISO C [-Wpedantic]».

person Ian Abbott    schedule 10.10.2016