C 中的指针何时计算出它引用的内存量?
When does a pointer in C figure out the amount of memory it references?
我一直在学习链表,节点结构的递归定义一直困扰着我
struct node {
struct node *next;
int data;
};
我想我一直都在想,既然指针是类型化的,那么它在声明时就知道在取消引用时可以访问的起始地址和内存量。但它不可能,因为它是在任意数量的其他变量之前声明的,这些变量可以构成任何大小的结构。它是仅在取消引用时才弄清楚,还是在结构定义的末尾和指针可以使用之前填充了某种内存table?
指针只是一个值,它在内存中保存一个地址。
编译器知道结构的大小和这些结构中字段的偏移量。每当您访问引用结构中的字段时,它都会添加一个偏移量。
看下面的程序:
struct X {
char a;
int b;
long c;
};
void y() {
struct X x;
x.a = 42;
x.b = 43;
x.c = 44;
}
函数 y
被翻译成以下汇编代码 (gcc -s
):
y:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movb , -16(%rbp)
movl , -12(%rbp)
movq , -8(%rbp)
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
您可以清楚地看到值 42、43、44。编译器计算了结构 x 中字段的偏移量。它们是相对于堆栈指针 (rbp) 的,因为值 x 是在堆栈上分配的。
结构体声明时,(以成员作为指向同一类型的指针)只需要知道类型,即指向类型的指针。也许在那个时候,类型还不完整,但是指向该类型的指针对于该平台来说是已知的。
换句话说,这与您不能在结构声明中包含该结构类型的成员的原因相同(此时,结构不 完整 )但可以有一个指向类型的指针。
一旦你让指针成员指向一些有效的内存,那么它也知道起始地址。然后,在取消引用时,它会根据前面提到的 "type" 计算偏移地址并从 that 位置获取值。
每个指针都有相同的大小,具体取决于您的系统(据我所知是 4 或 8 个字节)。
所以当你输入这样一个结构时
struct node {
struct node *next; // 4 or 8 bytes
int data; // 4 or 8 bytes
};
编译器知道它的大小。
但请尝试使用这种方式,然后自行查看。
//Wrong declaration
struct node {
struct node next; // The compiler cannot decide structure's size
int data; // 4 or 8 bytes
};
这会产生编译错误。
编辑
德普。现在我明白你实际问的问题了。
这有两个部分。首先,编译器允许您创建指向 "incomplete" 类型的指针,其中的大小尚不清楚。其次,所有指向 struct
类型的指针都具有相同的大小和表示,而不管实际 struct
类型的大小如何。
以你为例:
struct node {
struct node *next;
int data;
};
当编译器看到 next
的声明时,类型 struct node
是 不完整的 - 编译器还不知道有多大 struct node
会。但是,在此过程中,不需要 知道该大小即可声明指向该类型的指针。您还没有达到编译器需要知道 sizeof *next
的地步。
当编译器看到 struct
定义的结尾 };
时,类型定义就完成了——此时,编译器知道 struct node
类型实际上有多大。
原创
编译器知道 pointed-to 类型的大小,因此给定一个指针 p
,表达式 p + 1
将产生 [=63= 的下一个对象的地址]类型。
给出
int *ip = 0x1000; // 4 bytes
char *cp = 0x1000; // 1 byte
double *dp = 0x1000; // 8 bytes
表达式ip + 1
将产生下一个4字节int
对象的地址,或者0x1004
、cp + 1
将产生下一个1-字节的地址byte char
对象,或 0x1001
,dp + 1
将产生下一个 8 字节 double
对象,或 0x1008
.
的地址
指针本身指向一个对象,句号。它无法知道它指向的对象是否是序列的一部分,或者任何此类序列的大小。
我一直在学习链表,节点结构的递归定义一直困扰着我
struct node {
struct node *next;
int data;
};
我想我一直都在想,既然指针是类型化的,那么它在声明时就知道在取消引用时可以访问的起始地址和内存量。但它不可能,因为它是在任意数量的其他变量之前声明的,这些变量可以构成任何大小的结构。它是仅在取消引用时才弄清楚,还是在结构定义的末尾和指针可以使用之前填充了某种内存table?
指针只是一个值,它在内存中保存一个地址。
编译器知道结构的大小和这些结构中字段的偏移量。每当您访问引用结构中的字段时,它都会添加一个偏移量。
看下面的程序:
struct X {
char a;
int b;
long c;
};
void y() {
struct X x;
x.a = 42;
x.b = 43;
x.c = 44;
}
函数 y
被翻译成以下汇编代码 (gcc -s
):
y:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movb , -16(%rbp)
movl , -12(%rbp)
movq , -8(%rbp)
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
您可以清楚地看到值 42、43、44。编译器计算了结构 x 中字段的偏移量。它们是相对于堆栈指针 (rbp) 的,因为值 x 是在堆栈上分配的。
结构体声明时,(以成员作为指向同一类型的指针)只需要知道类型,即指向类型的指针。也许在那个时候,类型还不完整,但是指向该类型的指针对于该平台来说是已知的。
换句话说,这与您不能在结构声明中包含该结构类型的成员的原因相同(此时,结构不 完整 )但可以有一个指向类型的指针。
一旦你让指针成员指向一些有效的内存,那么它也知道起始地址。然后,在取消引用时,它会根据前面提到的 "type" 计算偏移地址并从 that 位置获取值。
每个指针都有相同的大小,具体取决于您的系统(据我所知是 4 或 8 个字节)。
所以当你输入这样一个结构时
struct node {
struct node *next; // 4 or 8 bytes
int data; // 4 or 8 bytes
};
编译器知道它的大小。
但请尝试使用这种方式,然后自行查看。
//Wrong declaration
struct node {
struct node next; // The compiler cannot decide structure's size
int data; // 4 or 8 bytes
};
这会产生编译错误。
编辑
德普。现在我明白你实际问的问题了。
这有两个部分。首先,编译器允许您创建指向 "incomplete" 类型的指针,其中的大小尚不清楚。其次,所有指向 struct
类型的指针都具有相同的大小和表示,而不管实际 struct
类型的大小如何。
以你为例:
struct node {
struct node *next;
int data;
};
当编译器看到 next
的声明时,类型 struct node
是 不完整的 - 编译器还不知道有多大 struct node
会。但是,在此过程中,不需要 知道该大小即可声明指向该类型的指针。您还没有达到编译器需要知道 sizeof *next
的地步。
当编译器看到 struct
定义的结尾 };
时,类型定义就完成了——此时,编译器知道 struct node
类型实际上有多大。
原创
编译器知道 pointed-to 类型的大小,因此给定一个指针 p
,表达式 p + 1
将产生 [=63= 的下一个对象的地址]类型。
给出
int *ip = 0x1000; // 4 bytes
char *cp = 0x1000; // 1 byte
double *dp = 0x1000; // 8 bytes
表达式ip + 1
将产生下一个4字节int
对象的地址,或者0x1004
、cp + 1
将产生下一个1-字节的地址byte char
对象,或 0x1001
,dp + 1
将产生下一个 8 字节 double
对象,或 0x1008
.
指针本身指向一个对象,句号。它无法知道它指向的对象是否是序列的一部分,或者任何此类序列的大小。