我正在寻找在c / c ++中定义const-promotion的位置。这是一个隐式转换,但是我找不到任何文档。
这在使用--pedantic标志时在g ++上有效
// prototypes for example
void foo(char*);
void bar(const char*);
char buffer[8];
snprintf(buffer,sizeof(buffer), "hello");
// note: string literals are of type const char*
foo(buffer);
foo("hello"); // works, but why
bar(buffer);
bar("hello");
上面表示的行为是预期的行为。但是,我正在寻找有关此行为的文档。我查看了c ++ 98标准(草稿),并在堆栈溢出中搜索“促销”和“隐式转换”,但没有找到答案。
如果这个问题过于笼统,我正在使用C ++ 98,因此我们可以针对该标准进行解决。
答案 0 :(得分:3)
此答案适用于C,而不适用于C ++。
字符字符串文字(与UTF-8字符串文字或宽字符串文字有所区别)是char
的数组,根据C 2018 6.4.5 6 1 。由于历史原因,它们不是const char
的数组,但是程序员应将它们视为const
,因为如果程序尝试写入字符串文字,则该行为不是C标准定义的
作为数组,字符串文字会自动转换为指向其第一个元素的char *
,除非它是sizeof
或一元&
的操作数,或者用于初始化数组。
因此,在foo(buffer)
和foo("hello")
中,我们都有一个char *
参数传递给了char *
参数,并且不需要进行转换。
在bar(buffer)
和bar("hello")
中,我们有一个char *
参数传递给了const *
参数。对此的解释如下。
对于可见原型的函数调用,根据C 2018 6.5.2.2 7,将参数转换为参数类型,就像通过赋值一样:
如果表示被调用函数的表达式的类型确实包含原型,则将参数隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型视为非限定版本其声明的类型。…
(请注意,“声明类型的非标准版本”是指const int
或char * const
参数分别为int
或char *
,而不是{{ 1}}参数为const char *
。)
6.5.16.1 2说:
在简单赋值(=)中,右操作数的值将转换为赋值表达式的类型…
赋值表达式的类型是左操作数的类型6.5.16 3:
…赋值表达式的类型是左值转换后左操作数将具有的类型。…
因此,现在我们知道char *
已转换为char *
。这也满足了6.5.16.1 1中分配的约束条件:
应满足以下条件之一:…左操作数具有原子,合格或不合格的指针类型,并且(考虑到左操作数在左值转换后将具有的类型)两个操作数都是指向兼容类型的合格或不合格版本的指针,并且左侧指向的类型具有右侧指向的类型的所有限定符;…
在6.3.2.3 2中指定了指针转换:
对于任何限定词 q ,指向非 q 限定类型的指针都可以转换为指向 q 限定类型的指针类型的版本;存储在原始指针和转换指针中的值应比较相等。
对于const char *
调用,参数snprintf
在与参数中的"hello"
对应的位置传递。为此,我们看一下6.5.2.2 7的其余部分,该部分从上面引用的第一部分继续:
…函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认的参数提升是对尾随参数执行的。
默认参数提升为6.5.2.2 6:
…对每个参数执行整数提升,并将类型为float的参数提升为双精度。这些称为默认参数提升。
那些提升不影响指针,因此以不变的类型传递指针。这很有趣,因为我们可以在此处传递...
或char *
。 const char *
的规范是指snprintf
,对于fprintf
规范,其在7.21.6.1 8中表示:
…参数应为指向字符类型数组的初始元素的指针。…
因此,它只需要一个指向“字符类型”的指针,而不是诸如%s
或char
或const char
之类的特定类型的指针。
(我们可能会进一步怀疑,是否要实现自己的函数,例如volatile char
并使用snprintf
来实现,是否传递<stdarg.h>
参数并用{{ 1}}宏调用会起作用。我在7.16.1.1 2中对char *
规范的初步阅读指出类型必须兼容,但是va_arg(ap, const char *)
和va_arg
不兼容,但是我有没有对此进行深入研究。)
1 从技术上讲,字符串文字是C转换阶段中源代码或其表示中的内容,它用于创建char *
的数组。为简单起见,我将数组称为字符串文字。