指针衰减的数组是否更改为指针对象?
Is the array to pointer decay changed to a pointer object?
int a[] = {1, 2 ,3};
我了解数组名称会转换为指针。经常使用的一个术语是它们衰减为指针。
但是对我来说,pointer
是一个内存区域,它保存了另一个内存区域的地址,所以:
int *p = a;
可以这样画:
----- -----
p ---------> a[0]. .....
----- -----
0x1 0x9
但是a
本身并没有指向另一个内存区域,它是内存区域本身。
因此,当编译器将其转换为指针时,它会将其保存在内存中的某个位置(如 p
)还是
这是一个隐式转换?
C 有对象和值。
值是一个抽象概念——它具有某种意义,通常是数学意义。数字具有 4、19.5 或 −3 等值。地址的值是内存中的位置。结构的值是被视为聚合的其成员的值。
值可以用在表达式中,例如3 + 4*5
。在表达式中使用值时,它们在 C 使用的计算模型中没有任何内存位置。这包括作为地址的值,例如 &x + 3
.
中的 &x
对象是内存区域,其内容可以表示值。声明 int *p = &x
将 p
定义为一个对象。为其预留内存,赋值&x
.
对于用int a[10]
声明的数组,a
是一个对象;这是为 10 int
个元素保留的所有内存。
在表达式中使用a
时,除了作为sizeof
或一元&
的操作数外,表达式中使用的a
会自动转换为它的第一个元素的地址,&a[0]
。这是一个值。没有为它保留内存;它不是一个对象。它可以在表达式中用作值,而无需为其保留任何内存。请注意,实际的 a
不会以任何方式转换;当我们说 a
被转换为一个指针时,我们的意思只是产生了一个地址供表达式使用。
以上描述的都是C使用的计算模型中的语义,也就是某种抽象计算机的语义。实际上,当编译器处理表达式时,它通常使用处理器寄存器来操作这些表达式中的值。处理器寄存器是一种内存形式(它们是设备中保留值的东西),但它们不是我们在不加限定地谈论“内存”时通常指的“主内存”。然而,编译器也可能根本没有任何内存中的值,因为它在编译期间计算部分或全部表达式,因此在程序执行时实际计算的表达式可能不包括名义上的所有值用 C 编写的表达式。编译器也可能在主存中有这些值,因为计算一个复杂的表达式可能会溢出处理器寄存器中可行的内容,因此部分表达式必须临时存储在主存中(通常在硬件堆栈上)。
"But a
itself is not pointing to another region of memory, it IS the region of memory itself.
"So when the compiler converts it to a pointer, does it save it (like p
) somewhere in memory or it's an implicit conversion?"
这是一个隐式转换。编译器不会在内存中创建一个单独的指针对象(您可以 f.e。以任何方式分配不同的内存地址)来保存第一个元素的地址。
标准状态(强调我的):
"Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined."
Source: ISO/IEC 9899:2018 (C18), 6.3.2.1/4
数组转换为指针类型的表达式,不是lvalue
。
编译器只是将 a
计算为 &a[0]
(指向 a[0]
的指针)。
"I understand that array names are converted to pointers."
数组并不总是转换为指向其第一个元素的指针。请看上面引述的第一部分。 F.e。当用作 &a
时,a
不会衰减到指向其第一个元素的指针。相反,它获得了指向整个数组 int (*)[3]
.
的指针
But a itself is not pointing to another region of memory, it IS the region of memory itself. So when the compiler converts it to a pointer, does it save it (like p) somewhere in memory or it's an implicit conversion?
从逻辑上讲,这是一个隐式转换 - 不要求实现具体化指针的永久存储。
在实现方面,取决于编译器。例如,下面是一段创建数组并打印其地址的简单代码:
#include <stdio.h>
int main( void )
{
int arr[] = { 1, 2, 3 };
printf( "%p", (void *) arr );
return 0;
}
当我在 Red Hat 系统上使用 gcc
为 x86-64 编译它时,我得到以下机器码:
GAS LISTING /tmp/ccKF3mdz.s page 1
1 .file "arr.c"
2 .text
3 .section .rodata
4 .LC0:
5 0000 257000 .string "%p"
6 .text
7 .globl main
9 main:
10 .LFB0:
11 .cfi_startproc
12 0000 55 pushq %rbp
13 .cfi_def_cfa_offset 16
14 .cfi_offset 6, -16
15 0001 4889E5 movq %rsp, %rbp
16 .cfi_def_cfa_register 6
17 0004 4883EC10 subq , %rsp
18 0008 C745F401 movl , -12(%rbp)
18 000000
19 000f C745F802 movl , -8(%rbp)
19 000000
20 0016 C745FC03 movl , -4(%rbp)
20 000000
21 001d 488D45F4 leaq -12(%rbp), %rax
22 0021 4889C6 movq %rax, %rsi
23 0024 BF000000 movl $.LC0, %edi
23 00
24 0029 B8000000 movl [=11=], %eax
24 00
25 002e E8000000 call printf
25 00
26 0033 B8000000 movl [=11=], %eax
26 00
27 0038 C9 leave
28 .cfi_def_cfa 7, 8
29 0039 C3 ret
30 .cfi_endproc
31 .LFE0:
33 .ident "GCC: (GNU) 7.3.1 20180712 (Red Hat 7.3.1-6)"
34 .section .note.GNU-stack,"",@progbits
第 17 行通过从堆栈指针减去 16 为数组分配 space(是的,数组中只有 3 个元素,应该只需要 12 个字节 - 我会让更熟悉的人使用 x86_64 架构解释原因,因为我会弄错)。
第 18、19 和 20 行初始化数组的内容。请注意,机器代码中没有 arr
变量 - 这都是根据当前帧指针的 offset 完成的。
第 21 行是转换发生的地方——我们将数组第一个元素的有效地址(存储在 %rbp
寄存器中的地址减去 12)加载到 %rax
寄存器中.然后该值(连同格式字符串的地址)被传递给 printf
。请注意,此转换的结果不会存储在寄存器以外的任何地方,因此下次写入 %rax
时它将丢失 - IOW,没有像存储一样为它预留永久存储空间已为数组内容预留。
同样,在 x86-64 上的 Red Hat 运行 中 gcc
就是这样做的。不同架构上的不同编译器会以不同的方式执行。
这是 2011 ISO C 标准 (6.3.2.1p3) 的内容:
Except when it is the operand of the sizeof
operator, or the
unary &
operator, or is a string literal used to initialize an
array, an expression that has type “array of type” is converted to
an expression with type “pointer to type” that points to the initial
element of the array object and is not an lvalue. If the array object
has register storage class, the behavior is undefined.
标准在这里使用 "converted" 一词,但这不是通常的转换方式。
通常,转换(隐式转换或由强制转换运算符指定的显式转换)将某种类型的表达式作为其操作数,并产生以下结果目标类型。结果由操作数的值决定。在大多数或所有情况下,您可以编写一个函数来做同样的事情。 (请注意,隐式和显式转换执行相同的操作;数组到指针的转换是隐式的这一事实并不是特别相关。)
在上述数组到指针转换的情况下,情况并非如此。数组对象的值由其元素的值组成——并且该值不包含有关数组存储地址的信息。
将其称为调整而不是转换可能会更清楚。该标准使用单词 "adjusted" 来指代数组类型参数到指针类型参数的编译时转换。例如,这个:
void func(int notReallyAnArray[42]);
真正的意思是:
void func(int *notReallyAnArray);
数组表达式到指针表达式的"conversion"也是类似的事情
另一方面,"conversion" 一词 不仅仅 表示类型转换。例如,标准在讨论 printf
格式字符串时使用 "conversion" 一词("%d"
和 "%s"
是 转换规范 )。
一旦你明白所描述的"conversion"实际上是一个编译时调整,将一种表达式转换为另一种表达式(不是值),它就不会那么混乱了。
题外话:
关于数组到指针转换的标准描述的一件有趣的事情是它谈论数组类型的表达式,但行为取决于"the array object"。非数组类型的表达式不一定有与之关联的对象(即,它不一定是左值)。但是每个数组表达式 都是 一个左值。在一种情况下(非值联合或结构表达式的数组成员的名称,特别是当函数 returns 结构值时),必须更新语言以保证始终如此,并且临时生命周期 的概念必须在 2011 年标准中引入。引用函数调用返回的结构的数组成员名称的语义在 1990 年和 1999 年的标准中一点也不明确。
int a[] = {1, 2 ,3};
我了解数组名称会转换为指针。经常使用的一个术语是它们衰减为指针。
但是对我来说,pointer
是一个内存区域,它保存了另一个内存区域的地址,所以:
int *p = a;
可以这样画:
----- -----
p ---------> a[0]. .....
----- -----
0x1 0x9
但是a
本身并没有指向另一个内存区域,它是内存区域本身。
因此,当编译器将其转换为指针时,它会将其保存在内存中的某个位置(如 p
)还是
这是一个隐式转换?
C 有对象和值。
值是一个抽象概念——它具有某种意义,通常是数学意义。数字具有 4、19.5 或 −3 等值。地址的值是内存中的位置。结构的值是被视为聚合的其成员的值。
值可以用在表达式中,例如3 + 4*5
。在表达式中使用值时,它们在 C 使用的计算模型中没有任何内存位置。这包括作为地址的值,例如 &x + 3
.
&x
对象是内存区域,其内容可以表示值。声明 int *p = &x
将 p
定义为一个对象。为其预留内存,赋值&x
.
对于用int a[10]
声明的数组,a
是一个对象;这是为 10 int
个元素保留的所有内存。
在表达式中使用a
时,除了作为sizeof
或一元&
的操作数外,表达式中使用的a
会自动转换为它的第一个元素的地址,&a[0]
。这是一个值。没有为它保留内存;它不是一个对象。它可以在表达式中用作值,而无需为其保留任何内存。请注意,实际的 a
不会以任何方式转换;当我们说 a
被转换为一个指针时,我们的意思只是产生了一个地址供表达式使用。
以上描述的都是C使用的计算模型中的语义,也就是某种抽象计算机的语义。实际上,当编译器处理表达式时,它通常使用处理器寄存器来操作这些表达式中的值。处理器寄存器是一种内存形式(它们是设备中保留值的东西),但它们不是我们在不加限定地谈论“内存”时通常指的“主内存”。然而,编译器也可能根本没有任何内存中的值,因为它在编译期间计算部分或全部表达式,因此在程序执行时实际计算的表达式可能不包括名义上的所有值用 C 编写的表达式。编译器也可能在主存中有这些值,因为计算一个复杂的表达式可能会溢出处理器寄存器中可行的内容,因此部分表达式必须临时存储在主存中(通常在硬件堆栈上)。
"But
a
itself is not pointing to another region of memory, it IS the region of memory itself."So when the compiler converts it to a pointer, does it save it (like
p
) somewhere in memory or it's an implicit conversion?"
这是一个隐式转换。编译器不会在内存中创建一个单独的指针对象(您可以 f.e。以任何方式分配不同的内存地址)来保存第一个元素的地址。
标准状态(强调我的):
"Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined."
Source: ISO/IEC 9899:2018 (C18), 6.3.2.1/4
数组转换为指针类型的表达式,不是lvalue
。
编译器只是将 a
计算为 &a[0]
(指向 a[0]
的指针)。
"I understand that array names are converted to pointers."
数组并不总是转换为指向其第一个元素的指针。请看上面引述的第一部分。 F.e。当用作 &a
时,a
不会衰减到指向其第一个元素的指针。相反,它获得了指向整个数组 int (*)[3]
.
But a itself is not pointing to another region of memory, it IS the region of memory itself. So when the compiler converts it to a pointer, does it save it (like p) somewhere in memory or it's an implicit conversion?
从逻辑上讲,这是一个隐式转换 - 不要求实现具体化指针的永久存储。
在实现方面,取决于编译器。例如,下面是一段创建数组并打印其地址的简单代码:
#include <stdio.h>
int main( void )
{
int arr[] = { 1, 2, 3 };
printf( "%p", (void *) arr );
return 0;
}
当我在 Red Hat 系统上使用 gcc
为 x86-64 编译它时,我得到以下机器码:
GAS LISTING /tmp/ccKF3mdz.s page 1
1 .file "arr.c"
2 .text
3 .section .rodata
4 .LC0:
5 0000 257000 .string "%p"
6 .text
7 .globl main
9 main:
10 .LFB0:
11 .cfi_startproc
12 0000 55 pushq %rbp
13 .cfi_def_cfa_offset 16
14 .cfi_offset 6, -16
15 0001 4889E5 movq %rsp, %rbp
16 .cfi_def_cfa_register 6
17 0004 4883EC10 subq , %rsp
18 0008 C745F401 movl , -12(%rbp)
18 000000
19 000f C745F802 movl , -8(%rbp)
19 000000
20 0016 C745FC03 movl , -4(%rbp)
20 000000
21 001d 488D45F4 leaq -12(%rbp), %rax
22 0021 4889C6 movq %rax, %rsi
23 0024 BF000000 movl $.LC0, %edi
23 00
24 0029 B8000000 movl [=11=], %eax
24 00
25 002e E8000000 call printf
25 00
26 0033 B8000000 movl [=11=], %eax
26 00
27 0038 C9 leave
28 .cfi_def_cfa 7, 8
29 0039 C3 ret
30 .cfi_endproc
31 .LFE0:
33 .ident "GCC: (GNU) 7.3.1 20180712 (Red Hat 7.3.1-6)"
34 .section .note.GNU-stack,"",@progbits
第 17 行通过从堆栈指针减去 16 为数组分配 space(是的,数组中只有 3 个元素,应该只需要 12 个字节 - 我会让更熟悉的人使用 x86_64 架构解释原因,因为我会弄错)。
第 18、19 和 20 行初始化数组的内容。请注意,机器代码中没有 arr
变量 - 这都是根据当前帧指针的 offset 完成的。
第 21 行是转换发生的地方——我们将数组第一个元素的有效地址(存储在 %rbp
寄存器中的地址减去 12)加载到 %rax
寄存器中.然后该值(连同格式字符串的地址)被传递给 printf
。请注意,此转换的结果不会存储在寄存器以外的任何地方,因此下次写入 %rax
时它将丢失 - IOW,没有像存储一样为它预留永久存储空间已为数组内容预留。
同样,在 x86-64 上的 Red Hat 运行 中 gcc
就是这样做的。不同架构上的不同编译器会以不同的方式执行。
这是 2011 ISO C 标准 (6.3.2.1p3) 的内容:
Except when it is the operand of the
sizeof
operator, or the unary&
operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
标准在这里使用 "converted" 一词,但这不是通常的转换方式。
通常,转换(隐式转换或由强制转换运算符指定的显式转换)将某种类型的表达式作为其操作数,并产生以下结果目标类型。结果由操作数的值决定。在大多数或所有情况下,您可以编写一个函数来做同样的事情。 (请注意,隐式和显式转换执行相同的操作;数组到指针的转换是隐式的这一事实并不是特别相关。)
在上述数组到指针转换的情况下,情况并非如此。数组对象的值由其元素的值组成——并且该值不包含有关数组存储地址的信息。
将其称为调整而不是转换可能会更清楚。该标准使用单词 "adjusted" 来指代数组类型参数到指针类型参数的编译时转换。例如,这个:
void func(int notReallyAnArray[42]);
真正的意思是:
void func(int *notReallyAnArray);
数组表达式到指针表达式的"conversion"也是类似的事情
另一方面,"conversion" 一词 不仅仅 表示类型转换。例如,标准在讨论 printf
格式字符串时使用 "conversion" 一词("%d"
和 "%s"
是 转换规范 )。
一旦你明白所描述的"conversion"实际上是一个编译时调整,将一种表达式转换为另一种表达式(不是值),它就不会那么混乱了。
题外话:
关于数组到指针转换的标准描述的一件有趣的事情是它谈论数组类型的表达式,但行为取决于"the array object"。非数组类型的表达式不一定有与之关联的对象(即,它不一定是左值)。但是每个数组表达式 都是 一个左值。在一种情况下(非值联合或结构表达式的数组成员的名称,特别是当函数 returns 结构值时),必须更新语言以保证始终如此,并且临时生命周期 的概念必须在 2011 年标准中引入。引用函数调用返回的结构的数组成员名称的语义在 1990 年和 1999 年的标准中一点也不明确。