当我们在C中声明一个静态向量时,指向这个向量的指针被存储了吗?

当我们在C中声明一个静态向量时,指向这个向量的指针被存储了吗?,c,pointers,vector,C,Pointers,Vector,我想知道 int v [10]; 除了10*sizeofint字节外,还向该向量分配一个指向整数的指针。否,没有额外的指针分配 如果在函数内部声明,则在堆栈上分配10 int的内存;如果在全局声明,则在数据段中分配10 int的内存。虽然v在概念上包含此内存块开头的地址,但它不需要任何额外的存储,因为在编译代码时v将消失。否,没有额外的指针分配 如果在函数内部声明,则在堆栈上分配10 int的内存;如果在全局声明,则在数据段中分配10 int的内存。虽然v在概念上包含此内存块开头的地址,但它不

我想知道

int v [10];

除了10*sizeofint字节外,还向该向量分配一个指向整数的指针。

否,没有额外的指针分配


如果在函数内部声明,则在堆栈上分配10 int的内存;如果在全局声明,则在数据段中分配10 int的内存。虽然v在概念上包含此内存块开头的地址,但它不需要任何额外的存储,因为在编译代码时v将消失。

否,没有额外的指针分配


如果在函数内部声明,则在堆栈上分配10 int的内存;如果在全局声明,则在数据段中分配10 int的内存。虽然v在概念上包含此内存块开头的地址,但它不需要任何额外的存储,因为在编译代码时v会消失。

从技术上讲,当声明v数组时,可以使用适合存储10个整数的地址范围偏移内存堆栈。如果使用编译器生成c指令的底层汇编代码,您可以看到这一点。程序集将地址存储到这些堆栈位置

使用x86-64 gcc 9.3,我使用您给出的示例的稍微扩展版本编译了这个程序集

int main(void){
    int v [10];
    v[0] = 0;
    v[1] = 1;
    return 0;
}
产生:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-48], 0
        mov     DWORD PTR [rbp-44], 1
        mov     eax, 0
        pop     rbp
        ret

告诉你每一条指令的含义超出了这个答案,但是你可以看到0和1是如何以适合int大小的偏移量被推送到堆栈的。编译器没有准确分配所需的最小空间的原因与属于编译器实现的其他优化指令有关

从技术上讲,当您声明v数组时,将使用适合存储10个整数的地址范围偏移内存堆栈。如果使用编译器生成c指令的底层汇编代码,您可以看到这一点。程序集将地址存储到这些堆栈位置

使用x86-64 gcc 9.3,我使用您给出的示例的稍微扩展版本编译了这个程序集

int main(void){
    int v [10];
    v[0] = 0;
    v[1] = 1;
    return 0;
}
产生:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-48], 0
        mov     DWORD PTR [rbp-44], 1
        mov     eax, 0
        pop     rbp
        ret

告诉你每一条指令的含义超出了这个答案,但是你可以看到0和1是如何以适合int大小的偏移量被推送到堆栈的。编译器没有准确分配所需的最小空间的原因与属于编译器实现的其他优化指令有关

理解这个问题有两个概念层面

首先,C是用抽象机器来定义的。在这个模型中,v标识一个10 int的数组。v不是指针;“v”是标识符,它标识的东西,命名为v,是一个10 int的数组,没有指针。如果在源代码中的表达式中使用v,而不是用sizeof表示其大小或用&表示其地址,则它会自动转换为指针。实际上,这个指针是根据需要制造的,它不在C标准描述的模型中,存储在任何地方或从任何地方加载。它只是在需要的时候制作的

其次,当C实现使用数组时,它们有各种引用内存的方式。如果数组是静态对象,因为它是在任何函数之外定义的,那么它通常被放置在为静态数据保留的内存段中的某个位置。C实现引用此内存的方式多种多样,包括:

编译器在对象文件中放入有关它要引用的内存位置的信息。当程序被链接和加载时,链接器和加载器调整此信息,并根据需要更改机器指令,以便它们引用对象的最终地址。这可能导致指令包含对象的完整绝对地址,或者指令引用对象到某个包含基址的寄存器的距离。 加载程序时,加载程序选择将数据放入内存的位置,并设置一个寄存器以包含数据的基址。当编译器编译程序时,它使用偏移量来编写引用静态对象的指令,偏移量指示对象与该基址的距离。 如果对象是自动的,而不是在函数中定义的静态的,没有static关键字,典型的C实现使用堆栈上的内存。此方法类似于上面描述的基址寄存器和偏移量方法,但基址寄存器是一个称为堆栈指针的特殊寄存器,在调用每个函数时对其进行调整,以指向仅为使用该特定函数调用而保留的内存

无论何时使用基址寄存器和偏移量方法,绝对地址都不会以程序观察到的方式存在。例如,一条指令可能包含一个位置引用,如汇编语言中描述的38sp,这意味着“堆栈指针指向的位置超出38字节”。处理器将获取内容
如果从内存中读取该位置的内容,则将38添加到堆栈指针,并获取。在这种情况下,绝对地址暂时存在于处理器内部的某个地方,在那里它进行38的加法。

有两个概念层次可以理解这个问题

首先,C是用抽象机器来定义的。在这个模型中,v标识一个10 int的数组。v不是指针;“v”是标识符,它标识的东西,命名为v,是一个10 int的数组,没有指针。如果在源代码中的表达式中使用v,而不是用sizeof表示其大小或用&表示其地址,则它会自动转换为指针。实际上,这个指针是根据需要制造的,它不在C标准描述的模型中,存储在任何地方或从任何地方加载。它只是在需要的时候制作的

其次,当C实现使用数组时,它们有各种引用内存的方式。如果数组是静态对象,因为它是在任何函数之外定义的,那么它通常被放置在为静态数据保留的内存段中的某个位置。C实现引用此内存的方式多种多样,包括:

编译器在对象文件中放入有关它要引用的内存位置的信息。当程序被链接和加载时,链接器和加载器调整此信息,并根据需要更改机器指令,以便它们引用对象的最终地址。这可能导致指令包含对象的完整绝对地址,或者指令引用对象到某个包含基址的寄存器的距离。 加载程序时,加载程序选择将数据放入内存的位置,并设置一个寄存器以包含数据的基址。当编译器编译程序时,它使用偏移量来编写引用静态对象的指令,偏移量指示对象与该基址的距离。 如果对象是自动的,而不是在函数中定义的静态的,没有static关键字,典型的C实现使用堆栈上的内存。此方法类似于上面描述的基址寄存器和偏移量方法,但基址寄存器是一个称为堆栈指针的特殊寄存器,在调用每个函数时对其进行调整,以指向仅为使用该特定函数调用而保留的内存


无论何时使用基址寄存器和偏移量方法,绝对地址都不会以程序观察到的方式存在。例如,一条指令可能包含一个位置引用,如汇编语言中所述的38sp,这意味着“堆栈指针指向的位置超出38字节”。处理器将获取堆栈指针的内容,向其中添加38个,如果从内存中读取该位置的内容,则进行提取。在这种情况下,绝对地址暂时存在于处理器内部某个地方,在那里它进行38的加法。

不是显式的。编译器在必要时从数组中获取指针,因此它可能被存储为处理器指令的一部分,而不是显式存储。必要时,编译器会从数组中获取指针,因此它可能会作为处理器指令的一部分存储。非常感谢,因此,当在代码中执行类似于*v+1=x的操作时,编译器会将其转换为类似于*&v[0]+1的形式。“静态堆”不是一个合适的术语。静态对象通常不在堆中维护。甚至malloc系列例程管理的内存通常也不是堆。确切地说,这就是为什么我们可以写“5[v]”而不是“v[5]”。但不建议这样做:.v不“概念上包含地址”。v是一个标识符。数组是一个对象。v标识数组。编译器拥有它所需要的关于对象位置的任何信息。v识别它;它不包含地址。在任何函数外部用int v[10]定义的数组;通常不在数据段中。首先,是否存在数据段是一个实现细节,而不是标准C模型的一部分。其次,数据段通常用于非零初始化对象。具有默认初始化的对象可能会在使用它的实现中进入BSS段。非常感谢,因此在代码中执行类似于*v+1=x的操作时,编译器会将其转换为类似于*&v[0]+1的形式。“静态堆”不是一个合适的术语。静态对象通常不在堆中维护。甚至malloc系列例程管理的内存通常也不是堆。确切地说,这就是为什么我们可以写“5[v]”而不是“v[5]”。但不建议这样做:.v不“概念上包含地址”。v是一个标识符。数组是一个对象。v标识数组。编译器拥有它所需要的关于对象位置的任何信息。v识别它;它不包含其地址。外部定义的数组
在int v[10]的任何函数之外;通常不在数据段中。首先,是否存在数据段是一个实现细节,而不是标准C模型的一部分。其次,数据段通常用于非零初始化对象。具有默认初始化的对象可能会在使用它的实现中进入BSS段。查看底层程序集是我没有想到的,谢谢!是的,我看到没有指针v存储在那里。看到下面的程序集是一个我没有想到的想法,谢谢!是的,我看到那里没有存储指针v