我正在通过 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/