什么时候使用 const void*?

When to use const void*?

我有一个非常简单的测试函数,我用它来弄清楚 const 限定符发生了什么。

int test(const int* dummy)
{
   *dummy = 1;
   return 0;
}

使用 GCC 4.8.3 时,这个问题向我抛出一个错误。 然而这个编译:

int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

所以似乎 const 限定符只有在我使用参数而不强制转换为其他类型时才有效。

最近我看到使用

的代码
test(const void* vpointer, ...)

至少对我而言,当我使用 void* 时,我倾向于将其转换为 char* 以用于堆栈中的指针运算或跟踪。 const void* 如何防止子程序函数修改 vpointer 指向的数据?

const int *var;

const 是一个 合同 。通过接收 const int * 参数,您 "tell" 您(被调用函数)的调用者将不会修改指针指向的对象。

你的第二个例子明确地打破了契约,放弃了 const 限定符,然后修改接收到的指针指向的对象。永远不要这样做。

此 "contract" 由编译器强制执行。 *dummy = 1 无法编译。演员是一种绕过它的方法,通过告诉编译器你真的知道你在做什么并让你去做。不幸的是 "I really know what I am doing" 通常情况并非如此。

const 也可以被编译器用来执行优化,否则无法执行优化。


未定义的行为注释:

请注意,虽然转换本身在技术上是合法的,但修改声明为 const 的值是未定义的行为。所以从技术上讲,原始函数是可以的,只要传递给它的指针指向声明为可变的数据。否则就是未定义行为。

在 post

末尾有更多相关信息

至于动机和使用,让我们采用 strcpymemcpy 函数的参数:

char* strcpy( char* dest, const char* src );
void* memcpy( void* dest, const void* src, std::size_t count );

strcpy 对 char 字符串进行操作,memcpy 对通用数据进行操作。虽然我使用 strcpy 作为示例,但以下讨论对于两者完全相同,但是 char *const char * 用于 strcpy 以及 void *const void * 用于 memcpy:

destchar * 因为在缓冲区 dest 中函数将放置副本。该函数将修改此缓冲区的内容,因此它不是常量。

srcconst char * 因为函数只读取缓冲区 src 的内容。它不会修改它。

只有通过查看函数的声明,调用者才能断言以上所有内容。根据合同 strcpy 不会修改作为参数传递的第二个缓冲区的内容。


constvoid 是正交的。以上所有关于 const 的讨论都适用于任何类型 (int, char, void, ...)

void * 在 C 中用于 "generic" 数据。


关于未定义行为的更多信息:

案例一:

int a = 24;
const int *cp_a = &a; // mutabale to const is perfectly legal. This is in effect
                      // a constant view (reference) into a mutable object

*(int *)cp_a = 10;    // Legal, because the object referenced (a)
                      // is declared as mutable

案例二:

const int cb = 42;
const int *cp_cb = &cb;
*(int *)cp_cb = 10;    // Undefined Behavior.
                       // the write into a const object (cb here) is illegal.

我从这些例子开始,因为它们更容易理解。从这里到函数参数只有一步:

void foo(const int *cp) {
    *(int *)cp = 10;      // Legal in case 1. Undefined Behavior in case 2
}

案例一:

int a = 0;
foo(&a);     // the write inside foo is legal

案例二:

int const b = 0;
foo(&b);     // the write inside foo causes Undefined Behavior

我必须再次强调:除非你真的知道自己在做什么,并且现在和将来从事代码工作的所有人都是专家并且理解这一点,并且你有良好的动力,除非所有以上都满足了,永远不要抛弃constness!!

int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

不,这行不通。丢弃常量性(使用真正的 const 数据)是 未定义的行为 并且您的程序可能会崩溃,例如,如果实现将 const 数据放入 ROM。 "it works" 的事实不会改变您的代码是 ill-formed.

的事实

At least for me, when I used void*, I tend to cast it to char* for pointer arithmetic in stacks or for tracing. How can const void* prevent subroutine functions from modifying the data at which vpointer is pointing?

A const void* 表示指向某些无法更改的数据的指针。是的,为了阅读它,您必须将其转换为具体类型,例如 char。但是我说的是阅读,而不是写作,这又是UB。

这方面的内容更深入 here。 C 允许您完全绕过 type-safety:防止这种情况是您的工作。

给定 OS 上的给定编译器可能会将其某些 const 数据放入 read-only 内存页中。如果是这样,尝试写入该位置将在硬件中失败,例如导致一般保护错误。

const 限定符只是意味着写有 未定义的行为 。这意味着语言标准允许程序在您这样做(或其他任何事情)时崩溃。尽管如此,如果您认为自己知道自己在做什么,C 会让您搬起石头砸自己的脚。

您无法阻止子例程重新解释您提供给它的位以及它想要的 运行 任何机器指令。您正在调用的库函数甚至可能是用汇编语言编写的。但是对 const 指针这样做是 未定义的行为 ,你真的不想调用 未定义的行为 .

在我的脑海中,一个罕见的例子可能有意义:假设你有一个传递句柄参数的库。它如何生成和使用它们?在内部,它们可能是指向数据结构的指针。所以这是一个你可能 typedef const void* my_handle; 的应用程序,所以如果你的客户试图取消引用它或错误地对其进行算术运算,编译器将抛出一个错误,然后将它转换回你的库函数中指向你的数据结构的指针。这不是最安全的实现,你要小心可以将任意值传递给你的库的攻击者,但它非常 low-overhead.