c - C 变量在底层包含哪些信息?

标签 c

我正在通过 K&R C 工作,这句话对我来说很突出:

A pointer is a variable that contains the address of a variable.

我总是假设(也许是错误的)底层的变量必须包含名称、类型和内存中某个位置的地址。即:虽然变量可以被视为值,但编译器必须知道这些值存储在内存中的位置,因此变量也必须是指针(概念上,而不是形式上)。

但现在我不太确定了。文本似乎暗示变量在某种程度上比指针更基本。

变量到底是什么?它们是否像引擎盖下的指针,或者它们在某些方面有所不同?特别希望在内存分配方式的背景下理解这一点。

编辑:对于那些参与语义辩论的人...我感兴趣的是理解_平均_用例,而不是标准指定或未指定的内容,尽管我应该指定。出于功能目的,假设在 unix 机器上使用 gcc 或类似工具编译 C。谢谢!

最佳答案

“变量”的具体构成因语言而异。使用什么样的运行时环境也很重要 - native 二进制文件 (C/C++/Fortran/Cobol/Pascal)、虚拟机中的字节码 (Java/C#/Scala/F#)、源代码级解释器 (old-skool) BASIC、bash/csh/sh) 等

就 C 而言,变量只是一 block 足够大以保存指定类型值的内存块 - 没有与该内存块关联的元数据来告诉您有关其名称的任何信息(通常不是保存在机器代码中),它的类型,是否是数组的一部分,等等。IOW,如果您在正在运行的程序中检查内存中的整数变量,您所看到的只是存储在该整数中的值。您不会看到存储有关该变量的任何其他信息。

在翻译期间(即,在编译代码时),编译器维护一个内部表,用于跟踪变量、变量名称、类型、范围、可见性等。但是,没有一个该信息(通常)会放入生成的机器代码中。 auto(本地)变量通常通过给定堆栈地址的偏移量来引用。 静态变量通常具有固定地址。不同类型的值使用不同的机器代码指令来处理(例如,通常有单独的指令来处理整数和 float )。

指针变量只存储一个地址。该地址的确切格式会因系统而异,但在现代 x86 和类似系统上,它本质上是一个无符号整数值。在分段内存系统上,它可能是一对值(页号和偏移量)。

编辑

C 代码通常被编译为 native 二进制文件(尽管至少有一个针对 Java VM 的编译器,并且可能存在针对其他虚拟机的编译器)。在类似 x86 的系统上,正在运行的 native 二进制文件通常在(虚拟!)内存中布局如下:

              +-------------------------+
High address: | Environmental variables |
              | and command line args   |
              +-------------------------+
              |        Stack            |
              |          |              |
              |          V              |
              |          ^              |
              |          |              |
              |         Heap            |
              +-------------------------+
              | Read-only data items    |
              +-------------------------+
              | Global data items       |
              +-------------------------+
              | Program text (machine   |
 Low address: | code)                   |
              +-------------------------+

确切的细节因系统而异,但这是一个不错的整体 View 。

每次调用函数(包括 main)时,都会从堆栈中获取内存以构建所谓的堆栈帧。堆栈帧包含函数参数(如果有)、局部变量(如果有)、前一个堆栈帧的地址以及函数返回后要执行的下一条指令的地址的空间。

              +--------------------+
High address: | Function arguments |
              +--------------------+
              | Return address     |
              +--------------------+
              | Prev frame address | <-- %rbp/%ebp (frame pointer)
              +--------------------+
 Low address: | Local variables    | <-- %rsp/%esp (stack pointer)
              +--------------------+ 

%rsp(64 位)或 %esp(32 位)寄存器存储堆栈顶部的地址(在 x86 上,堆栈增长“向下”向递减地址),%rbp(64 位)或 %ebp(32 位)寄存器存储堆栈帧的地址。函数参数和局部变量通过帧指针的偏移量来引用,例如

-4(%rpb) -- object starting 4 bytes "below" current frame address
32(%rbp) -- object starting 32 bytes "above" current frame address

这是一个示例 - 我们有一个函数 foo,它接受两个 int 参数并具有两个 int 局部变量:

#include  <stdio.h>

void foo( int x, int y )
{
  int a;
  int b;

  a = 2 * x + y;
  b = x - y;

  printf( "x = %d, y = %d, a = %d, b = %d\n", x, y, a, b );

}

这是为该函数生成的程序集(MacOS 10.13,LLVM 版本 9.1.0):

        .section        __TEXT,__text,regular,pure_instructions
        .macosx_version_min 10, 13
        .globl  _foo                    ## -- Begin function foo
        .p2align        4, 0x90
_foo:                                   ## @foo
        .cfi_startproc
## BB#0:
        pushl   %ebp
Lcfi0:
        .cfi_def_cfa_offset 8
Lcfi1:
        .cfi_offset %ebp, -8
        movl    %esp, %ebp
Lcfi2:
        .cfi_def_cfa_register %ebp
        pushl   %ebx
        pushl   %edi
        pushl   %esi
        subl    $60, %esp
Lcfi3:
        .cfi_offset %esi, -20
Lcfi4:
        .cfi_offset %edi, -16
Lcfi5:
        .cfi_offset %ebx, -12
        calll   L0$pb
L0$pb:
        popl    %eax
        movl    12(%ebp), %ecx
        movl    8(%ebp), %edx
        leal    L_.str-L0$pb(%eax), %eax
        movl    8(%ebp), %esi
        shll    $1, %esi
        addl    12(%ebp), %esi
        movl    %esi, -16(%ebp)
        movl    8(%ebp), %esi
        subl    12(%ebp), %esi
        movl    %esi, -20(%ebp)
        movl    8(%ebp), %esi
        movl    12(%ebp), %edi
        movl    -16(%ebp), %ebx
        movl    %eax, -24(%ebp)         ## 4-byte Spill
        movl    -20(%ebp), %eax
        movl    %eax, -28(%ebp)         ## 4-byte Spill
        movl    -24(%ebp), %eax         ## 4-byte Reload
        movl    %eax, (%esp)
        movl    %esi, 4(%esp)
        movl    %edi, 8(%esp)
        movl    %ebx, 12(%esp)
        movl    -28(%ebp), %esi         ## 4-byte Reload
        movl    %esi, 16(%esp)
        movl    %edx, -32(%ebp)         ## 4-byte Spill
        movl    %ecx, -36(%ebp)         ## 4-byte Spill
        calll   _printf
        movl    %eax, -40(%ebp)         ## 4-byte Spill
        addl    $60, %esp
        popl    %esi
        popl    %edi
        popl    %ebx
        popl    %ebp
        retl
        .cfi_endproc
                                        ## -- End function
        .section        __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
        .asciz  "x = %d, y = %d, a = %d, b = %d\n"


.subsections_via_symbols

这是我们的堆栈框架的样子:

              +---+
High address: | y |
              +---+
              | x |
              +---+
              |   | return address
              +---+
              |   | address of previous frame
              +---+
              | a |
              +---+
              | b |
              +---+

现在,这就是 32 位世界中的情况。 64 位变得有点复杂 - 一些函数参数在寄存器中而不是在堆栈上传递,因此上面漂亮整洁的图片崩溃了。

现在,我正在谈论运行时变量的概念,我认为这就是您所问的问题。

关于c - C 变量在底层包含哪些信息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51658307/

相关文章:

c - ARM 组装中的意外时间

c++ - libneo4j-client 无法关闭并重新打开新 session ?

c++ - 组合数组元素 Arduino

c++ - 为什么有些库使用非常量 char * 作为函数参数?

C编程sqrt函数

c - 不使用数学库的幂函数

c - 逗号分隔字符串的解析函数

c# - 相当于 C# 中的 C 结构

c - 如何将int与C中的队列进行比较?

c - 如何在从 Linux 启动时将 NCurses 输出定向到串行终端?