C 数组到指针衰减是否更改为指针对象?

C 数组到指针衰减是否更改为指针对象?,c,arrays,pointers,memory,language-lawyer,C,Arrays,Pointers,Memory,Language Lawyer,我知道数组名被转换为指针。经常使用的一个术语是,它们衰减为指针 然而,对我来说,指针是一个内存区域,它将地址保存到另一个内存区域,因此: int a[] = {1, 2 ,3}; 可以这样画: int *p = a; 但是a本身并没有指向另一个内存区域,而是内存区域本身。 因此,当编译器将其转换为指针时,它是否将其保存(如p)在内存或内存中的某个位置 这是一种隐式转换?C有对象和值 值是一个抽象的概念,它是某种意义,通常是数学上的。数字的值为4、19.5或−3.地址的值是内存中的位置。结构的

我知道数组名被转换为指针。经常使用的一个术语是,它们衰减为指针

然而,对我来说,
指针是一个内存区域,它将地址保存到另一个内存区域,因此:

int a[] = {1, 2 ,3};
可以这样画:

int *p = a;
但是
a
本身并没有指向另一个内存区域,而是内存区域本身。 因此,当编译器将其转换为指针时,它是否将其保存(如
p
)在内存或内存中的某个位置
这是一种隐式转换?

C有对象和值

值是一个抽象的概念,它是某种意义,通常是数学上的。数字的值为4、19.5或−3.地址的值是内存中的位置。结构的值是其成员作为聚合的值

值可以在表达式中使用,例如
3+4*5
。当在表达式中使用值时,它们在C使用的计算模型中没有任何内存位置。这包括作为地址的值,例如
&x+3
中的
&x

对象是内存区域,其内容可以表示值。声明
int*p=&x
p
定义为对象。为其保留内存,并为其分配值
&x

对于用
inta[10]
声明的数组,
a
是一个对象;它是为10个
int
元素保留的所有内存

当在表达式中使用
a
时,除了作为
sizeof
或一元
&
的操作数之外,表达式中使用的
a
会自动转换为其第一个元素
&a[0]
的地址。这是一个价值观。没有为它保留内存;它不是一个物体。它可以在表达式中作为值使用,而无需为其保留任何内存。请注意,实际的
a
不会以任何方式转换;当我们说
a
被转换为指针时,我们的意思是只生成一个地址用于表达式中

以上所有内容都描述了C使用的计算模型中的语义,即某些抽象计算机的语义。实际上,当编译器处理表达式时,它通常使用处理器寄存器来处理这些表达式中的值。处理器寄存器是内存的一种形式(它们是设备中保留值的东西),但它们不是我们在无条件地谈论“内存”时通常所指的“主内存”。但是,编译器也可能根本没有任何内存中的值,因为它在编译期间部分或全部计算表达式,因此,在程序执行时实际计算的表达式可能不包括用C编写的表达式中名义上的所有值。编译器也可能在主存中包含这些值,因为计算复杂表达式可能会溢出处理器寄存器中可行的值,因此,表达式的某些部分必须临时存储在主内存中(通常存储在硬件堆栈上)

但是
a
本身并不是指向另一个内存区域,而是内存区域本身

“那么,当编译器将其转换为指针时,它是将其保存(如
p
)在内存中的某个位置,还是隐式转换?”

这是一种隐式转换。编译器不会在内存中创建一个单独的指针对象(您可以使用不同的内存地址以任何方式分配该对象)来保存第一个元素的地址

标准规定(强调我的):

“除非是sizeof运算符的操作数或一元&运算符,或者是用于初始化数组的字符串文字,否则类型为“array of type”的表达式将转换为类型为“pointer to type”的表达式,该表达式指向数组对象的初始元素,不是左值。如果阵列对象具有寄存器存储类,则行为未定义。“

来源:ISO/IEC 9899:2018(C18),6.3.2.1/4

数组被转换为指针类型的表达式,它不是
左值

编译器只是将
a
计算为
&a[0]
(指针指向
a[0]


“我知道数组名被转换为指针。”

数组并不总是转换为指向其第一个元素的指针。请看上面引号的第一部分。例如,当用作
&a
时,
a
不会衰减为指向其第一个元素的指针。相反,它会获得指向整个数组的指针
int(*)[3]

但是a本身并没有指向另一个内存区域,它是内存区域本身。所以当编译器将它转换为指针时,它是将它(像p一样)保存在内存中的某个位置,还是隐式转换

从逻辑上讲,这是一种隐式转换——不需要实现指针的永久存储

就实现而言,这取决于编译器。例如,下面是一段简单的代码,用于创建数组并打印其地址:

-----              -----
  p    --------->  a[0].  .....
-----              -----
 0x1                0x9
第17行通过从堆栈指针中减去16为数组分配空间(是的,数组中只有3个元素,应该只需要12个字节-我将让更熟悉x86_64体系结构的人解释原因,因为我会弄错)

第18行、第19行和第20行初始化数组的内容。请注意,机器代码中没有
arr
变量-这都是根据与当前帧指针的偏移量来完成的

第21行是转换发生的地方-我们将数组第一个元素的有效地址(存储在
%rbp
寄存器中的地址减去12)加载到
%rax
寄存器中。该值(以及格式str的地址
#include <stdio.h>

int main( void )
{
  int arr[] = { 1, 2, 3 };
  printf( "%p", (void *) arr );
  return 0;
}
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    $16, %rsp
  18 0008 C745F401      movl    $1, -12(%rbp)
  18      000000
  19 000f C745F802      movl    $2, -8(%rbp)
  19      000000
  20 0016 C745FC03      movl    $3, -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    $0, %eax
  24      00
  25 002e E8000000      call    printf
  25      00
  26 0033 B8000000      movl    $0, %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
void func(int notReallyAnArray[42]);
void func(int *notReallyAnArray);