【问题标题】:address operator and array in assembly level汇编级别的地址运算符和数组
【发布时间】:2018-09-28 03:14:44
【问题描述】:

Q1:地址运算符获取该变量的地址。例如:

int var1;
int *p;
p = &var1;

程序在第 3 行将 var1 的地址传递给变量 p。

是不是CPU创建了一个空间来放var1的地址(由于地址操作符),然后交给p?

Q2:代码如下:

int A[3]= {2, 4, 6};

int *p = A;

printf("%d\n", p);
printf("%d\n", *p);

打印 p 的值和 p 的解引用值(A[1] 的值), 相当于:

int A[3]= {2, 4, 6};

printf("%d\n", A);
printf("%d\n", *A);

Q2的第一个例子很清楚,CPU创建了另一个变量p来存储A的地址。

除了创建一个变量 p 之外,第二个示例是否在 CPU 中做同样的事情?

谢谢

【问题讨论】:

  • 根据您使用的编译器(您没有指定),有一些方法可以让它输出它生成的程序集。我的期望是编译器能够对此进行大量优化。生成的程序集不一定必须为每个 C 变量指定位置。它们可能会结合在一起,或者完全优化掉。
  • 很确定这有您正在寻找的所有答案:stackoverflow.com/a/4955297/2495283
  • JFYI: *p*A 在后面的示例中指的是 A[0] 元素,而不是 A[1],在 C 中,数组索引从零开始。当您将其视为内存地址时,这是有道理的,如果A 是数组内容的第一个字节的内存地址,那么A+0 仍然是第一个元素,但A+1 被编译器调整为机器码@ 987654332@,即指向数组的第二个元素=开始后的一个元素。

标签: c assembly cpu


【解决方案1】:

CPU 不会“创建变量”。你可以说编译器通过选择它使用的指令在堆栈上为变量创建空间,或者在静态存储中使用汇编指令在.data.rodata或@987654324中保留空间@ 部分。

如果您将该代码放在 http://godbolt.org/ 上并禁用优化 (-O0),您会或多或少地看到您所期望的,即 x86、ARM 或其他任何东西的实际 asm 将为每个变量保留空间存在于 C 抽象机中,并将值实际存储在那里。

但是在正常编译的实际代码中(-O2-O3),编译器利用“好像”规则,它允许它发出任何它决定将产生与 C 源代码相同的可观察行为的 asm 指令,包括完全优化 int *p 并执行相当于 printf("%d\n", 2); 的操作。不过,将地址A 传递给像printf 这样的外部函数可能会导致编译器实际在堆栈上创建该数组。但是你永远不会获取p 本身的地址,所以p 没有理由存在于内存中。

【讨论】:

    【解决方案2】:

    恐怕你在这里弄错了两件事:

    • 地址运算符&的使用。
    • 在 C 中使用数组。

    & 运算符的地址允许您从与 value 相同类型的变量构造指向 value 的指针。这意味着,当你写:

    p = &var1;
    

    C获取变量var1的地址并将那个值存入指针变量p

    当你通过名字引用一个数组时,你得到的值就是第一个元素的地址,所以

    int *p = A;
    

    相同,获取A的第一个元素的地址并将该值分配给指向int变量p的指针

    当你这样做时:

    int *p = &A;
    

    您正在从指向int 数组的指针到int 类型数组的变量中进行赋值,这是一种不同的类型。从历史上看,编译器会自动转换类型并且没有给出任何指示,但由于它不正确,它可能会混淆不止一个。它对我的编译器发出警告,尽管:

    $ make pru2.o
    cc -O -pipe -c pru2.c -o pru2.o
    pru2.c:5:6: warning: incompatible pointer types initializing 'int *' with an expression of type 'int (*)[20]'
          [-Wincompatible-pointer-types]
    int *q = &A;
         ^   ~~
    1 warning generated.
    

    这体现在您稍后发布的代码中:

    printf("%d\n", A); /* this is the same ass &A[0] */
    printf("%d\n", *A); /* dereference that, as *(&A[0]) == A[0] */
    

    在这里,重要的是(在使用数组时)清楚地区分

    &A
    

    A
    

    是不同的表达式,它们给出(通常认为是未定义行为)相同的地址值,但作为指向不同类型的指针。

    &A  
    

    是数组A的地址,因此定义为类型

    int (*)[sizeof A / sizeof A[0]]
    

    同时

    A
    

    表示A的第一个元素的地址,因此它被定义为类型

    int *
    

    (指向int的指针)这可以通过简单地打印指向解引用的大小来显示,如下所示:

    int A[20];
    
    ...
    
    printf("sizeof *(&A) == %z\n", sizeof *(&A));
    

    printf("sizeof *A == %z\n", sizeof *A);
    

    (第一个会给你完整数组的大小,证明数组A的地址是一个指向20个数组的指针ints,而第二个会给你第一个的大小A 的元素,证明它指向单个 int 元素)

    一般来说,数组在 C 中的使用方式非常有限,因为它们在很多情况下都被同化为指针,它们不能按值传递 --- 使用数组名作为参数给函数传递一个指向第一个元素的指针,使用数组的地址使编译器传递相同的值,但作为指向 n 个元素的数组的指针---这将被被调用者视为简单的指针值函数,因为未指定的数组后缀[] 将被编译器转换为指针定义。

    【讨论】:

      【解决方案3】:

      Q1:不,地址运算符不是var1 在堆栈上有空间的原因。每当您“本地”声明任何变量时,“计算机”都会在堆栈上为该变量分配内存。

      Q2:是的。请注意,在某些 条件下,您可以交换数组和指针。也就是说,对于int [] my_arrayint * ptr*my_array*ptr 都会给你数组的第一个元素

      【讨论】:

      • 我想在 Q1 中了解的是:& 运算符是否需要内存空间?无论 var1 是全局的还是本地的。
      • @AndyLin 语言没有具体说明会发生什么。由每个实现决定何时分配内存。答案甚至可能会根据优化级别而改变。
      • @information_interchange Q1:如果你有一个像 int 这样的原语并且你从不获取它的地址,它可能只存在于寄存器中,而永远不会存在于实际堆栈中。
      • & 操作符在内存中不需要比 + 操作符更多的空间。
      猜你喜欢
      • 2020-12-02
      • 1970-01-01
      • 2014-07-02
      • 1970-01-01
      • 2014-08-09
      • 1970-01-01
      • 2020-12-04
      • 1970-01-01
      • 2015-06-27
      相关资源
      最近更新 更多