为什么我不能在C中将'char **'转换为'const char * const *'?

时间:2008-09-16 22:50:31

标签: c pointers const const-correctness

以下代码片段(正确)在C中发出警告,在C ++中发出错误(分别使用gcc和g ++,使用版本3.4.5和4.2.1进行测试; MSVC似乎并不关心):

char **a;
const char** b = a;

我能理解并接受这一点 这个问题的C ++解决方案是将b更改为const char * const *,它不允许重新分配指针并阻止你绕过const-correctness(C++ FAQ)。

char **a;
const char* const* b = a;

但是,在纯C中,更正后的版本(使用const char * const *)仍会发出警告,我不明白为什么。 有没有办法在不使用演员表的情况下解决这个问题?

澄清:
1)为什么这会在C中生成警告?它应该完全是const安全的,C ++编译器似乎也认识它 2)在说(并让编译器强制执行)我不会修改它指向的字符时,接受这个char **作为参数的正确方法是什么? 例如,如果我想写一个函数:

void f(const char* const* in) {
  // Only reads the data from in, does not write to it
}

我想在char **上调用它,参数的正确类型是什么?

编辑: 感谢那些做出回应的人,特别是那些提出问题和/或跟进我的回复的人。

我已经接受了这样一个答案,即如果没有演员表,我想做的事情是无法完成的。无论是否应该这样做。

6 个答案:

答案 0 :(得分:57)

几年前我遇到了同样的问题,这让我感到厌烦。

C中的规则更简单(即,它们不会列出将char**转换为const char*const*)等异常。结果,这是不允许的。使用C ++标准,它们包含更多规则以允许这样的情况。

最后,它只是C标准中的一个问题。我希望下一个标准(或技术报告)能解决这个问题。

答案 1 :(得分:10)

>但是,在纯C中,这仍然会发出警告,我不明白为什么

您已经确定了问题 - 此代码不是常量。 “Const correct”意味着,除了删除const的const_cast和C样式转换之外,你永远不能通过这些const指针或引用来修改const对象。

const-correctness-const的值在很大程度上是为了检测程序员错误。如果你将某些东西声明为const,那么你就是说你不认为它应该被修改 - 或者至少那些只能访问const版本的人不应该修改它。考虑:

void foo(const int*);

如声明的那样,foo没有权限来修改其参数指向的整数。

如果你不确定你发布的代码为什么不是const-correct,请考虑以下代码,与HappyDude的代码略有不同:

char *y;

char **a = &y; // a points to y
const char **b = a; // now b also points to y

// const protection has been violated, because:

const char x = 42; // x must never be modified
*b = &x; // the type of *b is const char *, so set it 
         //     with &x which is const char* ..
         //     ..  so y is set to &x... oops;
*y = 43; // y == &x... so attempting to modify const 
         //     variable.  oops!  undefined behavior!
cout << x << endl;

非const类型只能以特定的方式转换为const类型,以防止在没有显式强制转换的情况下对数据类型进行任何规避'const'。

最初声明为const的对象特别特殊 - 编译器可以假设它们永远不会改变。但是,如果'b'可以在没有强制转换的情况下赋值为'a',那么您可能会无意中尝试修改const变量。这不仅会破坏您要求编译器进行的检查,还会禁止您更改该变量值 - 它还允许您中断编译器优化!

在某些编译器上,这将在某些'43'上打印'42',而其他编译器将会崩溃。

修改-ADD

HappyDude:您的评论是现货。无论是C语言还是你正在使用的C语言编译器,对于const char * const *的根本不同于C ++语言对待它。也许只考虑为此源代码行静默编译器警告。

修改删除:删除拼写错误

答案 2 :(得分:10)

要被认为是兼容的,源指针应该是紧邻前向间接级别的const。所以,这会在GCC中给你警告:

char **a;
const char* const* b = a;

但这不会:

const char **a;
const char* const* b = a;

或者,你可以施展它:

char **a;
const char* const* b = (const char **)a;

如前所述,您需要使用相同的强制转换来调用函数f()。据我所知,在这种情况下无法进行隐式转换(C ++除外)。

答案 3 :(得分:1)

这很烦人,但如果您愿意添加另一个重定向级别,您通常可以执行以下操作来深入到指针指针:

char c = 'c';
char *p = &c;
char **a = &p;

const char *bi = *a;
const char * const * b = &bi;

它的含义略有不同,但它通常是可行的,并且它不使用强制转换。

答案 4 :(得分:0)

至少在MSVC 14(VS2k5)和g ++ 3.3.3上隐式将char **转换为const char * const *时,我无法收到错误。 GCC 3.3.3发出警告,我不确定它是否正确。

test.c的:

#include <stdlib.h> 
#include <stdio.h>
void foo(const char * const * bar)
{
    printf("bar %s null\n", bar ? "is not" : "is");
}

int main(int argc, char **argv) 
{
    char **x = NULL; 
    const char* const*y = x;
    foo(x);
    foo(y);
    return 0; 
}

以C代码编译输出:cl / TC / W4 / Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter

使用编译为C ++代码输出:cl / TP / W4 / Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter

使用gcc输出:gcc -Wall test.c

test2.c: In function `main':
test2.c:11: warning: initialization from incompatible pointer type
test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type

使用g ++输出:g ++ -Wall test.C

无输出

答案 5 :(得分:0)

我很确定const关键字并不意味着数据无法更改/是常量,只是数据将被视为只读。考虑一下:

const volatile int *const serial_port = SERIAL_PORT;

这是有效的代码。 volatile和const如何共存?简单。 volatile告诉编译器在使用数据时总是读取内存,而const告诉编译器在尝试使用serial_port指针写入内存时会产生错误。

const是否有助于编译器的优化器?一点都不。因为constness可以通过强制转换添加到数据中或从数据中删除,所以编译器无法确定const数据是否真的是常量(因为强制转换可以在不同的转换单元中完成)。在C ++中,您还可以使用mutable关键字来进一步复杂化。

char *const p = (char *) 0xb000;
//error: p = (char *) 0xc000;
char **q = (char **)&p;
*q = (char *)0xc000; // p is now 0xc000

当尝试写入真正只读的内存(例如ROM)时,会发生什么情况可能在标准中根本没有定义。