如何使用typeof_unqual避免编译器关于丢弃const限定符的警告

6yoyoihd  于 2023-10-16  发布在  其他
关注(0)|答案(5)|浏览(224)

在下面的代码中,copyThing()被编写为使其返回值与其参数值具有相同的类型:

extern void *copyThingImpl(const void *x);

#define copyThing(x) ((typeof(x)) copyThingImpl(x))

void foo(void);

void foo(void)
{
    char *x = "foo";
    char *y;

    y = copyThing(x);
    (void) y;
}

它使用广泛使用的typeof运算符。这可以正常工作,并且编译时没有警告(例如,gcc -Wall -O2 -c test.c)。
现在,如果我们将x更改为const指针,我们将遇到一些小问题:

extern void *copyThingImpl(const void *x);

#define copyThing(x) ((typeof(x)) copyThingImpl(x))

void foo(void);

void foo(void)
{
    const char *x = "foo";
    char *y;

    y = copyThing(x);
    (void) y;
}

这将导致警告:

warning: assignment discards 'const' qualifier from pointer target type

这是正确的,因为(typeof(x))(const char *),所以分配给非consty将丢弃const
我的理解是,C23中新的typeof_unqual运算符应该能够解决这种情况。所以我试

extern void *copyThingImpl(const void *x);

#define copyThing(x) ((typeof_unqual(x)) copyThingImpl(x))

void foo(void);

void foo(void)
{
    const char *x = "foo";
    char *y;

    y = copyThing(x);
    (void) y;
}

如果我使用适当的C23启用选项(例如,gcc -Wall -O2 -std=c2x -c test.c)编译它,我仍然会从各种gcc和clang版本中得到相同的警告。
我用这个正确吗?如果typeof_unqual不起作用,那么它还有什么意义呢?

bkhjykvo

bkhjykvo1#

如果你试图避免一个 discard const qualifier 警告,并试图通过强制转换来修复它,那么你的设计就会遇到严重的麻烦,因为你破坏了类型系统,而类型系统并不存在,而是帮助你本地化错误的类型使用。
const限定符是你添加的东西,让编译器尝试修改或更新一些数据。如果你把数据作为参数赋值/传递给一个非const的目标接收器,你会得到警告,因为最终受保护的数据不再受保护。**编译器只是提醒你。但是如果你告诉编译器 * 请忘记我荒谬的定义,因为我疯了,喜欢这样做 * 那么,为什么要将数据声明为const呢???
如果你有一些const *指针数据,并且你想给它赋值/别名,那么也可以通过将别名变量声明为const来实现。永远不要使用石膏。
我记得有一个特殊的情况,我必须将const *指针传递给free(3),但free在其定义中没有const *规范(可能是因为实现者假设在free()完成其工作后,释放的数据--以及指针本身--根本不可用,因此,它将更好地实现为void free(void *);而不是void free(const void *);,但这会阻止您在const void *指针上调用free,并且您将遇到这种问题)(这里对用例不感兴趣,所以我不会解释为什么我必须在分配的内存上使用const *,假设我必须在初始化后保护这些内存)
我解决了这个问题,在将指针传递给free(3)之前,我只是简单地转换为(void *),但我更希望free(3)的实现者在 * 内部 * Package ,然后将const void *参数 Package 为void *,以防他们更喜欢在最多时不可写的分配内存中实现一些写入。
在任何情况下,如果你要破坏类型系统,那么就使用一个转换为(void *),或者任何你方便的类型,然后在代码中根本不要使用const
如果您的功能:

extern void *copyThingImpl(const void *x);

在某些情况下,易受return原始传递指针值的影响,那么它应该被定义为:

extern const void *copyThingImpl(const void *x);

并在内部将所有char *定义更改为const char *。这是正确的,类型安全的方式做你想要的。
作为一般规则,在你接受的东西上尽量宽容(在参数中使用const,只有当你根本不接触数据时,但只有在那时),在你返回的东西上尽可能严格(例如,使用const如果由于某种原因,你返回的数据不能被改变,就好像你必须返回一个指针是const)一个const变量可以用非const数据初始化(这将使存储的数据在副本上不可修改),但反过来是不可能的。这是由编译器在传递给函数的每个赋值或参数中检查的。

vx6bjr1n

vx6bjr1n2#

typeof_unqual(x)只删除属于x本身的限定符,而不是x所指向的对象(如果它是指针)。
要从指向的类型中删除const,必须在宏中解引用x

#define copyThing(x) ((typeof_unqual(*(x)) *)copyThingImpl(x))

上面的版本works in recent versions of gcc and clang,但需要明确的c23标准和一些额外的标志,这两个都是编译器特定的。

vlf7wbxs

vlf7wbxs3#

你的代码的问题是变量(指针)x不是常量。它是常量指针所指向的字符串文字。

const char *x = "foo";

如果你写的是

const char * const x = "foo";

则在指针y的声明中将从指针x中丢弃限定符const
这样你就可以写作了

void foo(void)
{
    const char * const x = "foo";
    const char *y;

    y = copyThing(x);
    (void) y;
}

在这种情况下,表达式copyThing(x)将不是常量指针。
也就是说,typeof( x )const char * consttypeof_unqual( x )const char *
你可以写例如

typedef const char * T;

在这种情况下,const T等价于const char * const,从typedef名称const T中删除限定符const,您将得到const char *
在C23标准中有一个例子。声明了一个数组,如

const char* const animals[] = {
"aardvark",
"bluejay",
"catte",
};

这份声明

typeof_unqual(animals) animals2_array;

声明一个数组,如

const char* animals2_array[3];

它是指向字符串文字的非常量指针数组。
顺便说一下,注意在C中,与C++相反,字符串字面量(由于历史原因)具有非常量字符数组类型,尽管任何试图在C中改变字符串字面量的尝试都会导致未定义的行为。

4ngedf3f

4ngedf3f4#

这基本上只是旧的const char* ptr vs char*const ptr FAQ的一个新版本。*左边的所有内容都表示指向类型,*右边的所有内容都属于指针变量ptr本身。
因此,在const char* ptr的情况下,typeof_unqual(ptr)将留给你const char* ptr。但是如果ptrconst char*const ptr,我们最终得到const char*-指向的类型不受影响,但属于变量ptr本身的限定符被删除。
实际上,您可以通过(typeof_unqual(*x)*) copyThingImpl(x))来修复此表达式,因为我们允许在typeof/typeof_unqual之后添加星号。说明:

  • xconst char*
  • *xconst char
  • typeof_unqual(*x)char
  • typeof_unqual(*x)*char*
  • (typeof_unqual(*x)*)外括号使其成为强制转换。

抛开所有的“语言律师”不谈,像这样的宏可能不适合用于任何目的。如果实际意图是创建类型安全,那么宏应该声明为:

#define copyThing(x) ( (char*) _Generic((*x), char: copyThingImpl)(x) )

泛型表达式丢弃了限定符(我相信是按照C17)-6.5.1.1“控制表达式的类型是表达式的类型,就好像它经历了左值转换一样”。这意味着上面的宏将接受char*const char*(和volatile char*等)和数组等价物,但不接受指向其他类型的指针,包括void*
如果因为提供了正确的类型而编译代码,则copyThingImpl(x)的结果将转换为char*,在本示例中,char*是唯一接受的类型。

n8ghc7c1

n8ghc7c15#

typeof_unqual仅删除 * 顶级 * 限定符。所以它会转向

const char * const

const char *

但在您的示例中,const应用于指向的类型,而不是变量本身,因此它不是顶级限定符。

相关问题