在下面的代码中,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 *)
,所以分配给非const
y
将丢弃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
不起作用,那么它还有什么意义呢?
5条答案
按热度按时间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
。如果您的功能:
在某些情况下,易受
return
原始传递指针值的影响,那么它应该被定义为:并在内部将所有
char *
定义更改为const char *
。这是正确的,类型安全的方式做你想要的。作为一般规则,在你接受的东西上尽量宽容(在参数中使用
const
,只有当你根本不接触数据时,但只有在那时),在你返回的东西上尽可能严格(例如,使用const
如果由于某种原因,你返回的数据不能被改变,就好像你必须返回一个指针是const
)一个const
变量可以用非const
数据初始化(这将使存储的数据在副本上不可修改),但反过来是不可能的。这是由编译器在传递给函数的每个赋值或参数中检查的。vx6bjr1n2#
typeof_unqual(x)
只删除属于x
本身的限定符,而不是x
所指向的对象(如果它是指针)。要从指向的类型中删除
const
,必须在宏中解引用x
:上面的版本works in recent versions of gcc and clang,但需要明确的c23标准和一些额外的标志,这两个都是编译器特定的。
vlf7wbxs3#
你的代码的问题是变量(指针)
x
不是常量。它是常量指针所指向的字符串文字。如果你写的是
则在指针
y
的声明中将从指针x
中丢弃限定符const
。这样你就可以写作了
在这种情况下,表达式
copyThing(x)
将不是常量指针。也就是说,
typeof( x )
是const char * const
,typeof_unqual( x )
是const char *
。你可以写例如
在这种情况下,
const T
等价于const char * const
,从typedef名称const T
中删除限定符const
,您将得到const char *
。在C23标准中有一个例子。声明了一个数组,如
这份声明
声明一个数组,如
它是指向字符串文字的非常量指针数组。
顺便说一下,注意在C中,与C++相反,字符串字面量(由于历史原因)具有非常量字符数组类型,尽管任何试图在C中改变字符串字面量的尝试都会导致未定义的行为。
4ngedf3f4#
这基本上只是旧的
const char* ptr
vschar*const ptr
FAQ的一个新版本。*
左边的所有内容都表示指向类型,*
右边的所有内容都属于指针变量ptr
本身。因此,在
const char* ptr
的情况下,typeof_unqual(ptr)
将留给你const char* ptr
。但是如果ptr
是const char*const ptr
,我们最终得到const char*
-指向的类型不受影响,但属于变量ptr
本身的限定符被删除。实际上,您可以通过
(typeof_unqual(*x)*) copyThingImpl(x))
来修复此表达式,因为我们允许在typeof
/typeof_unqual
之后添加星号。说明:x
是const char*
。*x
是const char
。typeof_unqual(*x)
是char
。typeof_unqual(*x)*
是char*
。(typeof_unqual(*x)*)
外括号使其成为强制转换。抛开所有的“语言律师”不谈,像这样的宏可能不适合用于任何目的。如果实际意图是创建类型安全,那么宏应该声明为:
泛型表达式丢弃了限定符(我相信是按照C17)-6.5.1.1“控制表达式的类型是表达式的类型,就好像它经历了左值转换一样”。这意味着上面的宏将接受
char*
,const char*
(和volatile char*
等)和数组等价物,但不接受指向其他类型的指针,包括void*
。如果因为提供了正确的类型而编译代码,则
copyThingImpl(x)
的结果将转换为char*
,在本示例中,char*
是唯一接受的类型。n8ghc7c15#
typeof_unqual
仅删除 * 顶级 * 限定符。所以它会转向到
但在您的示例中,
const
应用于指向的类型,而不是变量本身,因此它不是顶级限定符。