何时使用const void *?

时间:2016-01-17 18:49:13

标签: c pointers type-safety

我有这个非常简单的测试功能,我用它来弄清楚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 指向的数据?

3 个答案:

答案 0 :(得分:45)

const int *var;

const合同。通过接收const int *参数,您“告诉”调用者您(被调用的函数)不会修改指针指向的对象。

你的第二个例子通过抛弃const限定符然后修改接收指针所指向的对象,显式地打破了那个契约。永远不要这样做。

此“合同”由编译器强制执行。 *dummy = 1将无法编译。通过告诉编译器你真的知道自己在做什么并让你这样做,演员是一种绕过它的方法。不幸的是,“我真的知道我在做什么”通常不是这样。

const也可以被编译器用来执行它本来无法实现的优化。

未定义的行为说明:

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

在帖子末尾更多关于此事

至于动机和使用,我们可以使用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 * { {1}}:

memcpydest,因为在缓冲区char *中,该函数将放置副本。该函数将修改此缓冲区的内容,因此它不是const。

destsrc,因为该函数只读取缓冲区const char *的内容。它不会修改它。

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

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

void在C中用于“通用”数据。

更多关于未定义的行为:

案例1:

void *

案例2:

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.

案例1:

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

案例2:

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

我必须再次强调:除非你真的知道自己在做什么,所有现在和将来在代码上工作的人都是专家并理解这一点,你有很好的动力,除非上述所有内容都是遇到了,永远不会抛弃常量!!

答案 1 :(得分:7)

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

不,这不起作用。抛弃const(具有真正的const数据)是未定义的行为,例如,如果实现将const数据放入ROM中,则程序可能会崩溃。 “它有效”的事实并没有改变你的代码格式不正确的事实。

  

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

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

深入探讨here。 C允许你完全绕过类型安全:这是你的工作,以防止这种情况。

答案 2 :(得分:3)

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

const限定符仅表示在那里写未定义的行为。这意味着语言标准允许程序在您执行(或其他任何操作)时崩溃。尽管如此,如果你认为自己知道自己在做什么,C会让你自己开枪。

你无法阻止一个子程序重新解释你给它的位,但它想要并运行它想要的任何机器指令。您调用的库函数甚至可以用汇编语言编写。但是对const指针执行此操作是未定义的行为,并且您实际上不想调用未定义的行为

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

相关问题