将const char**转换为void*?

9

考虑以下代码:

#include <memory.h>
#include <stdlib.h>

void Foo()
{
  const char ** caps = malloc( sizeof( char * ) );
  memset( caps, 0, sizeof( char * ) );
}

使用gcc 4.9.2-pedantic编译没有问题,但使用默认选项的cl 18(来自VS2013)在memset行上显示“warning C4090:'function':不同的'const'限定符”。

现在caps是一个指向指向常量字符的指针?因此,指针本身不是常量,因此应该可以将其转换为void*,但是cl似乎会自动从中生成const void*,从而生成警告。这是否是正在发生的事情的正确解释?这是非标准行为,对吗?


5
我认为这可能是微软编译器的一个 bug。虽然像这样的代码让我毛骨悚然,但从技术上讲,据我所知它是完全合法的... - Damon
2
就此而言,clang(3.4.1)在严格模式下也认为您的代码是正确的。 - jamesdlin
1
也许是这个?https://dev59.com/fuo6XIcBkEYKwwoYQiO7 - mafso
@mafso 很好的发现,正是这个。 - stijn
3个回答

3
编译器过于热情(即编译器错误)。 const char** caps表示caps是一个指针(不是常量),指向另一个指针(也不是常量),该指针指向一个常量char。也就是说,您承诺不通过经过caps的间接寻址修改那个char
这意味着您与编译器正式达成以下协议:
1. 您可以更改caps。 2. 您可以更改*caps(即caps指向的char*)。 3. 您不能通过这条链指针更改**caps(即*caps指向的charcaps指向的char))。 4. 没有关于其他人(例如别名指针)更改该字符值的说明。

const char **caps = malloc(sizeof(char*));

使用一个值初始化了caps,这是合法的。如果malloc失败,则该值为null指针,但从语言的角度来看,这也通常是完全合法的(尽管会导致以下的memset崩溃)。在C ++中,您需要显式转换malloc返回的void*,但是C语言可以很好地处理这种情况。
memset(caps, 0, sizeof(char*));

对于我这个C ++程序员来说,这让我毛骨悚然,但从C语言的角度来看,它仍然是完全合法的事情。
它所做的是用等于指针大小(恰好是一个char指针)的零字节数量覆盖到目前为止已分配但未初始化的(并由caps指向的)内存块中包含的第二个指针。

库函数memset接受一个非const void *,只需使用您提供的值(这里是零),就可以填充您要求的字节数(此处为sizeof(char *))。 它不关心你与编译器达成的合同,也不需要关心它。 但即使如此,它也没有违反任何规则。 它覆盖了指针,而不是指向的常量值。
是的,它将一些char值写入到不是char数组的东西中(这就是为什么我的头发竖起来的原因),但嗯...那是...合法的。 毕竟这正是memset应该做的事情,并且它很可能会“按预期工作”。 它将将指针设置为零位模式,这(除了在某些非常罕见的异构结构上)对应于空指针。

在任何时候都没有更改(甚至访问)内存位置**caps,因此所有这些都是完全合法的,您没有违反任何承诺。

因此,警告是错误的。


谢谢,这证实了我已有的想法。我没有找到如何在MS connect中搜索错误报告,所以我只好提交了一个新的 - 希望一旦修复了,这个问题就会得到解决:https://connect.microsoft.com/VisualStudio/feedback/details/1184080/warning-c4090-incorrectly-issued-for-conversion-from-const-char-to-void - stijn

-3

您不能将指向常量的指针(const X *)强制转换为void *:这会丢弃其const限定符。

memset需要修改这些数据,因此它需要一个void *参数而不是const void *参数。


3
我认为你没有理解重点。这里不是 const X* 而是 const X**。使用 memset 不会修改旨在成为 const 的部分。 - jamesdlin
2
但是,这不是一个指向常量的指针。它是一个指向常量的指针的指针。这个应该可以编译通过。使用 const char** 你承诺不会通过这个指针修改指向的指针所指向的 char。当 memset 修改指针时,这个承诺没有得到遵守。 - Damon
1
@nyarlathotep108 实际上,使用多少级间接引用确实很重要。 - The Paramagnetic Croissant
1
@nyarlathotep108 这是有很大的区别的。假设有 const char* p = "foo"; const char** pp = &p;,那么 memset(p, ...) 是非法的,因为它试图修改 p 所指向的 const char。这相当于 *p = '\0'。另一方面,memset(pp, ...) 应该是完全可以的,因为它修改的是 pp 所指向的指向常量的指针(请注意,指针本身不是 const); 这相当于 *pp = NULL。它只是使 pp 指向其他东西,而不是修改 const 数据。 - jamesdlin
1
memset根本看不到任何const。它看到的是一个非常量的void*,并且使用(char) 0x00填充sizeof(pointer)字节,尽管从您的角度来看,指向的内存甚至不是char类型!遗憾的是,memset不关心(也不需要关心)。但是类型的判断仍然存在。这是一个非常量指针,指向另一个非常量指针(被覆盖,合法),指向一个常量char - Damon
显示剩余4条评论

-3

你的写法中,const 关键字指向的是 char 而不是指针。显然,仅仅为了声明一个永远不会被修改(也就是永远不会被初始化)的变量而分配内存是没有意义的。

如果你想让指针本身成为 const,应该这样写:

char ** const caps

你也可以将第二个指针声明为const,但这样做与将char声明为const没有什么区别。


stjin 正在为指向 const char 的指针分配内存;该指针本身并不是 const,可以被赋予某些被认为是 const 的内容的地址。这种用法对我来说很有意义。 - jamesdlin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接