c/c++指针的内存地址在哪里?

c/c++指针的内存地址在哪里?,c++,c,operating-system,C++,C,Operating System,如果我这样做: int i, *p = &i; int **p2p = &p; 我在堆栈上获取I的地址并将其传递给p,然后在堆栈上获取p的地址并将其传递给p2p 我的问题是,我们知道i的值保存在内存地址p等等,但是操作系统如何知道该地址在哪里?我想他们的地址在堆栈中是有组织的。是否将每个声明的变量标识符视为堆栈当前位置的偏移量?关于全局变量,操作系统和/或编译器在执行过程中如何处理寻址这些变量?操作系统和编译器如何在不使用内存的情况下“记住”每个标识符的地址?是否所有变量都是按

如果我这样做:

int i, *p = &i;
int **p2p = &p;
我在堆栈上获取I的地址并将其传递给p,然后在堆栈上获取p的地址并将其传递给p2p


我的问题是,我们知道i的值保存在内存地址p等等,但是操作系统如何知道该地址在哪里?我想他们的地址在堆栈中是有组织的。是否将每个声明的变量标识符视为堆栈当前位置的偏移量?关于全局变量,操作系统和/或编译器在执行过程中如何处理寻址这些变量?操作系统和编译器如何在不使用内存的情况下“记住”每个标识符的地址?是否所有变量都是按顺序输入到堆栈中的,并且它们的名称都替换为它们的偏移量?如果是这样,那么可以更改声明顺序的条件代码呢

我曾经是一名汇编语言程序员,所以我知道我曾经使用的CPU的答案。主要的一点是,CPU的一个寄存器被用作堆栈指针,现在x86 CPU上称为SP或esp。编译器在您的例子中引用变量i、p和p2p与SP的关系。换句话说,编译器决定每个变量与SP的偏移量,并相应地生成机器代码。

我曾经是一名汇编语言程序员,因此我知道我使用的CPU的答案。主要的一点是,CPU的一个寄存器被用作堆栈指针,现在x86 CPU上称为SP或esp。编译器在您的案例中引用变量i、p和p2p,这些变量与SP有关。换句话说,编译器决定每个变量与SP的偏移量,并相应地生成机器代码。

操作系统不关心程序使用的地址。每当发出需要在地址空间中使用缓冲区的系统调用时,程序都会提供缓冲区的地址

编译器为每个函数提供一个堆栈框架

push ebp
mov ebp,esp
然后,任何函数参数或局部变量都可以相对于EBP寄存器的值进行寻址,EBP寄存器是堆栈帧的基址。这由编译器通过特定于编译器的引用表来处理

退出函数后,编译器会撕掉堆栈帧:

mov esp,ebp
pop ebp
在较低级别,CPU仅处理相同但使用方式不同的文本字节/字/DWORD/etc值和地址

所需的内存地址要么存储在命名的缓冲区中,例如编译器在编译时用其已知地址替换的全局变量,要么存储在CPU的寄存器中,非常简单,但仍然正确


对于操作系统开发,如果您愿意,我很乐意更深入地解释我所知道的一切,但这肯定超出了软件的范围,因此如果您感兴趣,我们需要找到另一个渠道。

操作系统不关心您的程序使用的地址。每当发出需要在地址空间中使用缓冲区的系统调用时,程序都会提供缓冲区的地址

编译器为每个函数提供一个堆栈框架

push ebp
mov ebp,esp
然后,任何函数参数或局部变量都可以相对于EBP寄存器的值进行寻址,EBP寄存器是堆栈帧的基址。这由编译器通过特定于编译器的引用表来处理

退出函数后,编译器会撕掉堆栈帧:

mov esp,ebp
pop ebp
在较低级别,CPU仅处理相同但使用方式不同的文本字节/字/DWORD/etc值和地址

所需的内存地址要么存储在命名的缓冲区中,例如编译器在编译时用其已知地址替换的全局变量,要么存储在CPU的寄存器中,非常简单,但仍然正确

对于操作系统开发,如果您愿意,我很乐意更深入地解释我所知道的一切,但这肯定超出了软件的范围,因此如果您感兴趣,我们需要找到另一个渠道

i的值保存在内存地址p等中,但操作系统如何知道该地址在哪里

操作系统既不知道也不关心变量在哪里

我认为[variables']地址在堆栈中被组织起来

堆栈不组织变量的地址。它只包含/保存变量的值


是否将每个声明的变量标识符视为堆栈当前位置的偏移量

对于某些局部变量来说,这可能确实成立。然而,优化可以将变量移动到CPU寄存器中,也可以完全消除它们

关于全局变量,操作系统和/或编译器在执行过程中如何处理寻址这些变量

编译器不处理变量wh 这个程序已经编译好了。它已经完成了它的工作

操作系统和编译器如何在不使用内存的情况下“记住”每个标识符的地址

操作系统不记得这些。它甚至对程序的变量一无所知。对于操作系统来说,您的程序只是一些无定形的代码和数据的集合。变量名没有意义,在编译程序中很少可用。只有程序员和编译器才需要它们。CPU和操作系统都不需要它们


是否所有变量都是按顺序输入到堆栈中的,并且它们的名称都替换为它们的偏移量

这将是一个合理的局部变量简化模型

如果是这样,那么可以更改声明顺序的条件代码呢

这就是编译器必须处理的问题。一旦程序被编译,所有的都被处理好了

i的值保存在内存地址p等中,但操作系统如何知道该地址在哪里

操作系统既不知道也不关心变量在哪里

我认为[variables']地址在堆栈中被组织起来

堆栈不组织变量的地址。它只包含/保存变量的值


是否将每个声明的变量标识符视为堆栈当前位置的偏移量

对于某些局部变量来说,这可能确实成立。然而,优化可以将变量移动到CPU寄存器中,也可以完全消除它们

关于全局变量,操作系统和/或编译器在执行过程中如何处理寻址这些变量

当程序已经编译时,编译器不处理变量。它已经完成了它的工作

操作系统和编译器如何在不使用内存的情况下“记住”每个标识符的地址

操作系统不记得这些。它甚至对程序的变量一无所知。对于操作系统来说,您的程序只是一些无定形的代码和数据的集合。变量名没有意义,在编译程序中很少可用。只有程序员和编译器才需要它们。CPU和操作系统都不需要它们


是否所有变量都是按顺序输入到堆栈中的,并且它们的名称都替换为它们的偏移量

这将是一个合理的局部变量简化模型

如果是这样,那么可以更改声明顺序的条件代码呢


这就是编译器必须处理的问题。一旦程序被编译,所有的问题都会得到解决。

正如@随机解释的那样:

编译器在您的例子中引用变量i、p和p2p 换句话说,编译器决定偏移量是多少 每个变量的值应来自SP,并生成机器代码 因此

也许这个例子可以为您另外解释一下。它位于amd64上,因此指针的大小为8字节。正如您所看到的,并没有变量,只有来自寄存器的偏移量

#include <cstdlib>
#include <stdio.h>
using namespace std;

/*
 * 
 */
int main(int argc, char** argv) {

    int i, *p = &i;
    int **p2p = &p;
    printf("address 0f i: %p",p);//0x7fff4d24ae8c
    return 0;
}
拆卸:

!int main(int argc, char** argv) {
main(int, char**)+0: push   %rbp
main(int, char**)+1: mov    %rsp,%rbp
main(int, char**)+4: sub    $0x30,%rsp
main(int, char**)+8: mov    %edi,-0x24(%rbp)
main(int, char**)+11: mov    %rsi,-0x30(%rbp)
!
!    int i, *p = &i;
main(int, char**)+15: lea    -0x4(%rbp),%rax
main(int, char**)+19: mov    %rax,-0x10(%rbp)  //8(pointer)+4(int)=12=0x10-0x4
!    int **p2p = &p;
main(int, char**)+23: lea    -0x10(%rbp),%rax
main(int, char**)+27: mov    %rax,-0x18(%rbp) //8(pointer)
!    printf("address 0f i: %p",p);//0x7fff4d24ae8c
main(int, char**)+31: mov    -0x10(%rbp),%rax //this is pointer
main(int, char**)+35: mov    %rax,%rsi //get address of variable, value would be %esi
main(int, char**)+38: mov    $0x4006fc,%edi
main(int, char**)+43: mov    $0x0,%eax
main(int, char**)+48: callq  0x4004c0 <printf@plt>
!    return 0;
main(int, char**)+53: mov    $0x0,%eax
!}
main(int, char**)()
main(int, char**)+58: leaveq 
main(int, char**)+59: retq 

正如@随机解释的那样:

编译器在您的例子中引用变量i、p和p2p 换句话说,编译器决定偏移量是多少 每个变量的值应来自SP,并生成机器代码 因此

也许这个例子可以为您另外解释一下。它位于amd64上,因此指针的大小为8字节。正如您所看到的,并没有变量,只有来自寄存器的偏移量

#include <cstdlib>
#include <stdio.h>
using namespace std;

/*
 * 
 */
int main(int argc, char** argv) {

    int i, *p = &i;
    int **p2p = &p;
    printf("address 0f i: %p",p);//0x7fff4d24ae8c
    return 0;
}
拆卸:

!int main(int argc, char** argv) {
main(int, char**)+0: push   %rbp
main(int, char**)+1: mov    %rsp,%rbp
main(int, char**)+4: sub    $0x30,%rsp
main(int, char**)+8: mov    %edi,-0x24(%rbp)
main(int, char**)+11: mov    %rsi,-0x30(%rbp)
!
!    int i, *p = &i;
main(int, char**)+15: lea    -0x4(%rbp),%rax
main(int, char**)+19: mov    %rax,-0x10(%rbp)  //8(pointer)+4(int)=12=0x10-0x4
!    int **p2p = &p;
main(int, char**)+23: lea    -0x10(%rbp),%rax
main(int, char**)+27: mov    %rax,-0x18(%rbp) //8(pointer)
!    printf("address 0f i: %p",p);//0x7fff4d24ae8c
main(int, char**)+31: mov    -0x10(%rbp),%rax //this is pointer
main(int, char**)+35: mov    %rax,%rsi //get address of variable, value would be %esi
main(int, char**)+38: mov    $0x4006fc,%edi
main(int, char**)+43: mov    $0x0,%eax
main(int, char**)+48: callq  0x4004c0 <printf@plt>
!    return 0;
main(int, char**)+53: mov    $0x0,%eax
!}
main(int, char**)()
main(int, char**)+58: leaveq 
main(int, char**)+59: retq 

从概念上讲,数据可以存储在内存的4个不同区域,这取决于它的范围以及它是常量还是变量。我从概念上说是因为内存分配非常依赖于平台,为了尽可能提高现代体系结构所能提供的效率,策略可能变得极其复杂

认识到这一点也很重要,除了少数例外,操作系统不知道或不关心变量所在的位置;中央处理器会。CPU负责处理程序中的每个操作、计算地址以及读写内存。事实上,操作系统本身只是一个程序,有自己的变量,由CPU执行

通常,编译器决定为每个变量分配哪种类型的内存,例如堆栈、堆、寄存器。如果它选择了一个寄存器,它还决定分配哪个寄存器。如果它选择另一种类型的内存,它将计算变量从该内存段开始的偏移量。它创建一个对象文件,该文件仍然将这些变量作为从其部分开始的偏移引用

然后链接器读取每个对象文件,将它们的变量组合并排序到适当的部分,然后修复偏移量。这是专业术语。真的

恒定数据 这是什么? 由于这些数据从不更改,因此通常与程序本身一起存储在 只读存储器。在嵌入式系统(如微波炉)中,这可能是在传统上便宜的ROM中,而不是在更昂贵的RAM中。在PC机上,它是一段RAM,只有操作系统才将其指定为就绪,因此尝试写入它将导致一个分段错误,并在程序非法更改不应该更改的内容之前停止程序

如何访问它? 编译器通常引用常量数据作为从常量数据段开始的偏移量。链接器知道段的实际位置,因此它修复段的起始地址

全局和静态数据 这是什么? 这些数据必须在运行程序的整个生命周期内都可用,因此它必须驻留在分配给程序的内存堆上。由于数据可以更改,堆不能像常量数据一样驻留在只读内存中;它必须驻留在可写RAM中

如何访问它? CPU访问全局和静态数据的方式与访问常量数据的方式相同:它被引用为堆开始的偏移量,堆的起始地址由链接器固定

本地数据 这是什么? 这些变量仅在封闭函数处于活动状态时存在。它们驻留在动态分配的RAM中,然后在函数退出时立即返回到系统。从概念上讲,它们是从一个堆栈中分配的,该堆栈随着函数的调用和变量的创建而增长;它会随着每个函数的返回而收缩。堆栈还保存每个函数调用的返回地址:CPU记录其在程序中的当前位置,并在调用函数之前将该地址推送到堆栈上;然后,当函数返回时,它会将地址从堆栈中弹出,这样它就可以从函数调用之前的任何位置恢复。但是,实际的实现取决于架构;重要的是要记住,函数的本地数据将变得无效,因此在函数返回后不应被引用

如何访问它? 本地数据通过从堆栈开始的偏移量进行访问。当编译器进入一个函数时,它知道下一个可用的堆栈地址,忽略一些深奥的情况,它还知道局部变量需要多少内存,因此它移动堆栈指针跳过该内存。然后,它通过计算堆栈中的地址来引用每个局部变量

登记册 它们是什么? 寄存器是CPU内部的一小块内存。所有计算都发生在寄存器中,寄存器操作非常快。CPU包含的寄存器数量相对较少,因此它们是有限的资源

如何访问它们? CPU可以直接访问寄存器,这使得寄存器操作非常快速。编译器可以选择将寄存器分配给变量作为优化,因此在将数据提取或写入RAM时无需等待。通常,只有本地数据被分配到寄存器。例如,循环计数器可能位于寄存器中,堆栈指针本身就是寄存器

#include <cstdlib>
#include <stdio.h>
using namespace std;

/*
 * 
 */
int main(int argc, char** argv) {

    int i, *p = &i;
    int **p2p = &p;
    printf("address 0f i: %p",p);//0x7fff4d24ae8c
    return 0;
}
你的问题的答案是: 在堆栈上声明变量时,编译器计算其大小并为其分配内存,从堆栈上的下一个可用位置开始。让我们看看您的示例,做出以下假设: 1.调用函数时,SP是堆栈中的下一个可用地址,该地址向下增长。 2.sizeofint=2只是为了使其不同于指针的大小。 3.sizeofint*=sizeofint**=4也就是说,所有指针的大小都相同。 然后: 您正在声明3个变量: i:Addr=SP,size=2,contents=uninitialized p:Addr=SP-2,size=4,contents=i的SP地址 p2p:Addr=SP-6,size=4,contents=p的SP-2地址


从概念上讲,数据可以存储在内存的4个不同区域,这取决于它的范围以及它是常量还是变量。我从概念上说是因为内存分配非常依赖于平台,为了尽可能提高现代体系结构所能提供的效率,策略可能变得极其复杂

认识到这一点也很重要,除了少数例外,操作系统不知道或不关心变量所在的位置;中央处理器会。CPU负责处理程序中的每个操作、计算地址以及读写内存。事实上,操作系统本身只是一个程序,有自己的变量,由CPU执行

通常,编译器决定为每个变量分配哪种类型的内存,例如堆栈、堆、寄存器。如果它选择了一个寄存器,它还决定分配哪个寄存器。如果它选择另一种类型的内存,它将计算变量从该内存段开始的偏移量。它创建一个对象文件,该文件仍然将这些变量作为从其部分开始的偏移引用

然后链接器读取每个对象 t文件,将其变量组合并排序到适当的部分,然后修复偏移量。这是专业术语。真的

恒定数据 这是什么? 由于这些数据从不更改,因此通常与程序一起存储在只读内存区域中。在嵌入式系统(如微波炉)中,这可能是在传统上便宜的ROM中,而不是在更昂贵的RAM中。在PC机上,它是一段RAM,只有操作系统才将其指定为就绪,因此尝试写入它将导致一个分段错误,并在程序非法更改不应该更改的内容之前停止程序

如何访问它? 编译器通常引用常量数据作为从常量数据段开始的偏移量。链接器知道段的实际位置,因此它修复段的起始地址

全局和静态数据 这是什么? 这些数据必须在运行程序的整个生命周期内都可用,因此它必须驻留在分配给程序的内存堆上。由于数据可以更改,堆不能像常量数据一样驻留在只读内存中;它必须驻留在可写RAM中

如何访问它? CPU访问全局和静态数据的方式与访问常量数据的方式相同:它被引用为堆开始的偏移量,堆的起始地址由链接器固定

本地数据 这是什么? 这些变量仅在封闭函数处于活动状态时存在。它们驻留在动态分配的RAM中,然后在函数退出时立即返回到系统。从概念上讲,它们是从一个堆栈中分配的,该堆栈随着函数的调用和变量的创建而增长;它会随着每个函数的返回而收缩。堆栈还保存每个函数调用的返回地址:CPU记录其在程序中的当前位置,并在调用函数之前将该地址推送到堆栈上;然后,当函数返回时,它会将地址从堆栈中弹出,这样它就可以从函数调用之前的任何位置恢复。但是,实际的实现取决于架构;重要的是要记住,函数的本地数据将变得无效,因此在函数返回后不应被引用

如何访问它? 本地数据通过从堆栈开始的偏移量进行访问。当编译器进入一个函数时,它知道下一个可用的堆栈地址,忽略一些深奥的情况,它还知道局部变量需要多少内存,因此它移动堆栈指针跳过该内存。然后,它通过计算堆栈中的地址来引用每个局部变量

登记册 它们是什么? 寄存器是CPU内部的一小块内存。所有计算都发生在寄存器中,寄存器操作非常快。CPU包含的寄存器数量相对较少,因此它们是有限的资源

如何访问它们? CPU可以直接访问寄存器,这使得寄存器操作非常快速。编译器可以选择将寄存器分配给变量作为优化,因此在将数据提取或写入RAM时无需等待。通常,只有本地数据被分配到寄存器。例如,循环计数器可能位于寄存器中,堆栈指针本身就是寄存器

#include <cstdlib>
#include <stdio.h>
using namespace std;

/*
 * 
 */
int main(int argc, char** argv) {

    int i, *p = &i;
    int **p2p = &p;
    printf("address 0f i: %p",p);//0x7fff4d24ae8c
    return 0;
}
你的问题的答案是: 在堆栈上声明变量时,编译器计算其大小并为其分配内存,从堆栈上的下一个可用位置开始。让我们看看您的示例,做出以下假设: 1.调用函数时,SP是堆栈中的下一个可用地址,该地址向下增长。 2.sizeofint=2只是为了使其不同于指针的大小。 3.sizeofint*=sizeofint**=4也就是说,所有指针的大小都相同。 然后: 您正在声明3个变量: i:Addr=SP,size=2,contents=uninitialized p:Addr=SP-2,size=4,contents=i的SP地址 p2p:Addr=SP-6,size=4,contents=p的SP-2地址


现代操作系统在较低的层次上使用,没有变量的概念。这是我们在编程时为了让生活更轻松而做的抽象。雷格德:除了不能回答这个问题之外,这也是不对的。仅仅因为主要的桌面操作系统使用虚拟内存并不能使这个笼统的说法更加有效。至于问题:变量和标识符是为了程序员的方便。CPU只看到内存地址及其包含的值。是否将每个声明的变量标识符视为堆栈当前位置的偏移量是的,对于堆栈变量。全局变量呢?在链接器和加载程序上读取。是否所有输入的变量都按顺序推入堆栈,并且它们的名称都替换为它们的偏移量这在概念上有点混乱。变量名表示偏移量
堆栈上的ts;他们没有被推。调用函数时会将堆栈帧添加到堆栈中,但没有推送,堆栈只会在返回时增长和收缩。如果是,那么可以更改声明顺序的条件代码呢条件代码无法更改任何内容。。。改变条件就像编写一个单独的程序。现代操作系统在较低的级别使用,没有变量的概念。这是我们在编程时为了让生活更轻松而做的抽象。雷格德:除了不能回答这个问题之外,这也是不对的。仅仅因为主要的桌面操作系统使用虚拟内存并不能使这个笼统的说法更加有效。至于问题:变量和标识符是为了程序员的方便。CPU只看到内存地址及其包含的值。是否将每个声明的变量标识符视为堆栈当前位置的偏移量是的,对于堆栈变量。全局变量呢?在链接器和加载程序上读取。是否所有输入的变量都按顺序推入堆栈,并且它们的名称都替换为它们的偏移量这在概念上有点混乱。变量名表示堆栈上的偏移量;他们没有被推。调用函数时会将堆栈帧添加到堆栈中,但没有推送,堆栈只会在返回时增长和收缩。如果是,那么可以更改声明顺序的条件代码呢条件代码无法更改任何内容。。。更改条件就像编写一个单独的程序。实际上使用的是EBP,而不是AMD64体系结构上的ESP或RBP。[SS:EBP+0]指向保存的EBP本身,[SS:EBP+4]指向返回地址,[SS:EBP+8]指向第一个参数,[SS:EBP+12]指向第二个参数,依此类推……实际上使用的是EBP,而不是AMD64体系结构上的ESP或RBP。[SS:EBP+0]指向保存的EBP本身,[SS:EBP+4]指向返回地址,[SS:EBP+8]指向第一个参数,[SS:EBP+12]指向第二个参数,依此类推……注意:如果常量标识符定义为文字数字值,例如:const int width=640;名称宽度将由编译器替换为值640。如果值是在运行时确定的:const int width=get_width;然后将这些值存储在内存中。注意:如果将常量标识符定义为文字数字值,例如:const int width=640;名称宽度将由编译器替换为值640。如果值是在运行时确定的:const int width=get_width;然后这些值存储在内存中。