编译器用a 做什么?a是数组? 如果a是指针怎么办?

c-faq告诉我,编译器在处理a [i]时做了不同的事情,而a是数组或指针。 这是c-faq的一个例子:

char a[] = "hello"; char *p = "world"; 

鉴于上面的声明,当编译器看到表达式a [3]时,它会发出代码从位置“a”开始,移动三个,然后在那里获取字符。 当它看到表达式p [3]时,它会发出代码从位置“p”开始,在那里获取指针值,向指针添加三个,最后获取指向的字符。

但有人告诉我,在处理[i]时,编译器倾向于将a(这是一个数组)转换为指向数组的指针。 所以我想查看汇编代码以找出哪个是正确的。

编辑:

这是本声明的来源。 c-faq并注意这句话:

formsa [i]的表达式导致数组衰减成指针,遵循上面的规则,然后被下标,就像表达式p [i]中的指针变量一样(尽管最终的内存访问将是不同,“

我对此很困惑:既然a已经衰减到指针,那为什么他的意思是“内存访问会有所不同?”

这是我的代码:

 // array.cpp #include  using namespace std; int main() { char a[6] = "hello"; char *p = "world"; printf("%c\n", a[3]); printf("%c\n", p[3]); } 

这是我使用g ++ -S array.cpp获得的汇编代码的一部分

  .file "array.cpp" .section .rodata .LC0: .string "world" .LC1: .string "%c\n" .text .globl main .type main, @function main: .LFB2: leal 4(%esp), %ecx .LCFI0: andl $-16, %esp pushl -4(%ecx) .LCFI1: pushl %ebp .LCFI2: movl %esp, %ebp .LCFI3: pushl %ecx .LCFI4: subl $36, %esp .LCFI5: movl $1819043176, -14(%ebp) movw $111, -10(%ebp) movl $.LC0, -8(%ebp) movzbl -11(%ebp), %eax movsbl %al,%eax movl %eax, 4(%esp) movl $.LC1, (%esp) call printf movl -8(%ebp), %eax addl $3, %eax movzbl (%eax), %eax movsbl %al,%eax movl %eax, 4(%esp) movl $.LC1, (%esp) call printf movl $0, %eax addl $36, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret 

我无法从上面的代码中找出[3]和p [3]的机制。 如:

  • “你好”在哪里初始化?
  • $ 1819043176是什么意思? 也许这是“你好”(a的地址)的内存地址?
  • 我确定“-11(%ebp)”意味着[3],但为什么呢?
  • 在“movl -8(%ebp),%eax”中,poniter p的内容存储在EAX中,对吧? 所以$ .LC0表示指针p的内容?
  • “movsbl%al,%eax”是什么意思?
  • 并且,请注意以下3行代码:
    movl $ 1819043176,-14(%ebp)
    movw $ 111,-10(%ebp)
    movl $ .LC0,-8(%ebp)

    最后一个使用“movl”,但为什么不覆盖-10(%ebp)的内容? (我现在知道anser :),地址是增量的,“movl $ .LC0 -8(%ebp)只会覆盖{-8,-7,-6,-5}(%ebp))

对不起,我对这个机制以及汇编代码感到很困惑……

非常感谢您的帮助。

a是指向字符数组的指针。 p是指向char的指针,在这种情况下,指向字符串文字。

 movl $1819043176, -14(%ebp) movw $111, -10(%ebp) 

初始化堆栈上的本地“hello”(这就是它通过ebp引用的原因)。 由于“hello”中有超过4个字节,因此需要两个指令。

 movzbl -11(%ebp), %eax movsbl %al,%eax 

参考文献a[3] :两步过程是因为通过ebp引用的内存访问方面的限制(我的x86-fu有点生疏)。

movl -8(%ebp), %eax确实引用了p指针。

LC0引用“相对存储器”位置:一旦程序加载到存储器中,将分配固定存储器位置。

movsbl %al,%eax表示:“移动单字节,降低”(给予或接受……我必须查找它……我在这方面有点生疏)。 al表示寄存器eax一个字节。

从语言方面来说,因为汇编方已经处理过了:

注意这句话:“a [i]forms的表达式导致数组衰减成指针,遵循上面的规则,然后被下标,就像表达式p [i]中的指针变量一样(尽管最终的内存访问会有所不同,“我对此非常困惑:因为a已经衰减到指针,那么为什么他的意思是”内存访问会有所不同?

这是因为衰减之后 ,对于(现在是指针值)和指针的访问是相等的。 但不同之处在于首先如何得到指针值。 我们来看一个例子:

 char c[1]; char cc; char *pc = &cc; 

现在,你有一个数组。 除了一个char之外,这个数组不会占用任何存储空间! 没有存储指针 。 你有一个指向char的指针。 指针采用一个地址的大小,并且指针指向一个char。 现在让我们看一下数组大小写会发生什么,以获取指针值:

 c[0] = 'A'; // #1: equivalent: *(c + 0) = 'A'; // #2: => 'c' appears not in address-of or sizeof // #3: => get address of "c": This is the pointer value P1 

指针大小写不同:

 pc[0] = 'A'; // #1: equivalent: *(pc + 0) = 'A'; // #2: => pointer value is stored in 'pc' // #3: => thus: read address stored in 'pc': This is the pointer value P1 

正如您所看到的,对于获取指针值所需的数组大小写我们添加索引值(在这​​种情况下是无聊的0 ),我们不需要从内存中读取,因为数组的地址已经是指针需要的价值。 但是对于指针的情况,我们需要的指针值存储在指针中:我们需要从内存中读取一个来获取该地址。

在此之后,两者的路径相同:

 // #4: add "0 * sizeof(char)" to P1. This is the address P2 // #5: store 'A' to address P2 

这是为数组和指针大小写生成的汇编程序代码:

  add $2, $0, 65 ; write 65 into r2 stb $2, $0, c ; store r2 into address of c # pointer case follows ldw $3, $0, pc ; load value stored in pc into r3 add $2, $0, 65 ; write 65 into r2 stb $2, $3, 0 ; store r2 into address loaded to r3 

我们可以在c的地址处存储65 (ASCII表示'A' )(在编译或链接时它已经是全局的)。 对于指针的情况,我们首先必须将它存储的地址加载到寄存器3 ,然后将65写入该地址。

虽然数组不是指针,但它们的行为非常相似。 在这两种情况下,编译器在内部将地址存储到类型化元素,并且在两种情况下都可以存在一个或多个元素。

在数组和指针中,当[]运算符取消引用时,编译器通过将索引乘以数据类型的大小并将其添加到指针或数组的地址来评估要索引的元素的地址。

指针和数组之间的根本区别在于数组本质上是一个引用。 如果将指针初始化为null或更改指针存储的值是合法的,则数组不能为null,并且不能将它们设置为其他数组; 它们本质上是常量指针,不能设置为null。

另外,可以在堆栈上分配数组,这对指针来说是不可能的(虽然指针可以设置为堆栈上的地址,但这可能会变得很丑陋)。

这些定义看起来很相似,但实际上却完全不同。

假设您的数组在函数内声明:

 void f() { char a[] = "hello"; char *p = "world"; } 

在第一种情况下,’a’衰减为指向STACK上6个字符的const指针。 在第二种情况下,’p’是一个非const指针,指向CONST区域(数据段)中的6个字符。

写作非常合法:

 a[3] = 'L'; 

 p[3] = 'L'; 

看起来正确,但会导致内存冲突,因为字符数组不在堆栈上,而是在只读部分。

此外,

 a++ 

是非法的(’a’衰减到一个常量指针,这是一个r值),但是

 p++ 

是合法的(p是l值)。