C 将宏的容器_应用于嵌入式字符数组时报告警告

C 将宏的容器_应用于嵌入式字符数组时报告警告,c,gcc,macros,gcc-warning,C,Gcc,Macros,Gcc Warning,当我将宏的container\u应用于包含字符数组的C结构时,我得到了警告:从不兼容的指针类型初始化 代码如下: #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct st {

当我将宏的
container\u应用于包含字符数组的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;
}
似乎
\uu mptr
的类型是
[]
,这是由
typeof()
推导出来的。但是,
ptr
本身是类型
(*)[]
。显然,它们是不一样的

此外,如果我用clang编译这段代码,一切都正常。GCC似乎有一个更严格的类型检查规则


问:如何更正此警告

声明
\uu mptr
是无用的,因为它只是在下一行中转换为
char*
。只需将宏替换为:

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

注意:GCC 6.2.0没有对原始代码给出任何警告,除非对于未使用的变量
s

数组类型本身不是
const
,而
const
实际上是来自
const
的每个成员。实际上,没有语法允许您声明数组本身是
const
。您可以阅读更多详细信息

然而,GCC中的
typeof
宏扩展似乎存在缺陷,因为如果
typeof
解析为数组类型,则
const
限定符将应用于数组,而不是其单个成员。(注意到该问题似乎已在较新版本的GCC中得到解决。)

我不知道你会认为什么是可接受的解决方案,但是下面的编译没有注意到的警告。

const struct st *ct = &t;
typeof(ct->c) *p = &ct->c;
struct st *s = container_of(p, struct st, c);

如果您知道传递给
container\u的成员是一个数组,则可以传递该数组的一个元素,以避免出现警告:

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的更高版本不会为原始代码生成警告“不兼容指针类型”。但是,在更高版本的GCC中启用
-Wpedantic
会产生警告“指向具有不同限定符的数组的指针在ISO C[-Wpedantic]中不兼容”。

您的错误消息应该包括实际类型。
(*)[]
不是一种类型,
[]
也不是。对于您的回答,这里的
[]
代表数组类型,
(*)[]
代表指向数组的指针。我很难相信clang接受此代码。至少,它依赖于一个扩展来实现这一点。宏扩展为一个带括号的代码块,而这不是一个表达式——它根本不表示值。@JohnBollinger是的,它依赖于两个GCC扩展。我相信,这个宏定义来自Linux内核(至少它目前在Linux内核中使用)。Windows NT头中的等效宏是
包含_RECORD
,当然它不使用任何GCC扩展。Linux内核中
宏的
容器有点缺陷,但是如果必须使用它,并且您知道成员是数组,则可以使用数组的元素而不是数组本身。即
char*p=&t.c[0];struct st*s=容器(p,struct st,c[0])
如果
常量
是唯一的问题,那么简单地删除它应该是一个可行的选择,因为
常量
特性会立即消失。我能想到的唯一原因是处理这样的情况:宏的
ptr
参数是指向引用类型的
const
限定版本的指针,但即使如此,安德烈·萨西的建议似乎更合适。@JohnBollinger:我的建议仅适用于不需要更改宏定义的情况。