正如在" How does pointer incrementation work?"中提到的那样,我有一个后续问题。
指针如何知道它指向的数据的基础大小?指针是否存储基础类型的大小,以便他们知道如何递增?
我希望以下代码将指针向前移动一个字节:
int intarr[] = { ... };
int *intptr = intarr;
intptr = intptr + 1;
printf("intarr[1] = %d\n", *intptr);
根据链接网站上接受的答案,指针按字节递增而不是由基础sizeof
指向的元素会导致大规模的歇斯底里,混乱和混乱。
虽然我知道这可能是一个不可避免的结果,但我仍然不明白指针在这方面的作用。我无法向某个void
类型数组声明struct[]
指针,如果我这样做,void
指针如何知道增加sizeof(struct mytype)
?< / p>
编辑:我相信我已经解决了我遇到的大部分困难,但就我在代码中展示它而言,我还不够。
见这里:http://codepad.org/0d8veP4K
#include <stdio.h>
int main(int argc, char *argv[])
{
int intarr[] = { 0, 5, 10 };
int *intptr = intarr;
// get the value where the pointer points
printf("intptr(%p): %d\n", intptr, *intptr);
printf("intptr(%p): %d\n", intptr + 1, *(intptr + 1));
printf("intptr(%p): %d\n", intptr + 2, *(intptr + 2));
// the difference between the pointer value should be same as sizeof(int)
printf("intptr[0]: %p | intptr[1]: %p | difference: %d | expected: %d",
intptr, intptr + 1, (intptr + 1) - intptr, sizeof(int));
return 0;
}
答案 0 :(得分:5)
它在类型声明中。 p1
知道类型的大小,因为它是sizeof(*p1)
或sizeof(int)
。 p2
不知道sizeof(void)
未定义。
int *p1;
void *p2;
p1++; // OK
p2++; // Not defined behavior in C
答案 1 :(得分:2)
指针是否存储基础类型的大小,以便他们知道如何递增?
这个问题表明,类型信息需要在运行时与对象保持一致,以便就如何对类型执行正确的操作做出正确的决定。这不是真的。类型信息成为代码的一部分。
如果我们在混合中添加第三种类型可能更容易理解:浮点。
考虑这个示例程序:
int a,b,c;
float x,y,z;
void f(void)
{
c = a+b*3;
z = x+y*3;
}
(我要求你首先考虑float
与int
的情况并不是因为它更简单,而是因为它更复杂。额外的复杂性使你无法采取诱人但又错误的捷径。)
编译器必须将f
转换为执行两种不同类型的加法和乘法的汇编代码。虽然相同的运算符(+
和*
)在C代码中出现两次,但汇编代码看起来不那么对称。前半部分将使用处理器的整数寄存器,整数加法指令和整数乘法指令,后半部分将使用浮点寄存器,浮点加法和浮点乘法。即使常数3
在它出现的两个地方也会有不同的表示。
在程序集级别,存储a
,b
,c
,x
,y
和z
的内存不存在不需要标记,因为类型信息隐含在访问该内存的指令中。整数寄存器的加载和存储仅针对持有a
,b
和c
的内存位置。
C算术运算符过载。当从具有重载运算符的语言转换为没有相应重载运算符的语言时,来自第一语言的类型信息成为第二语言的运算符名称的一部分。 (当从C ++转换为C时,“名称修改”在另一个级别发生同样的事情。你可以说汇编语言“ADD”(整数)和“FADD”(浮点)指令是名称错误的+
运算符。)
现在,关于指针算术。指针只是另一种超载类型。如果表达式a=a+1
可以生成两种不同类型的汇编代码,具体取决于a
是int
还是float
,为什么a
int *
时为什么不是第三种? {1}},a
为struct tm *
时的另一个,依此类推?
在C代码中,类型信息包含在变量声明中。在编译器的中间表示中,每个表达式的类型都是已知的。在编译器的输出中,必要的类型信息隐含在机器指令中。
答案 2 :(得分:1)
有点粗略的答案,但值得注意的是在机器级别,数据类型,正如我们在C中所知,不存在。我们可能具有对存储在某些通用寄存器中的整数进行操作的算术指令,例如,但是没有任何存储来识别某些寄存器的内容实际上是int
。所有机器看到的是各种类型内存中的一堆位和字节。
所以你甚至可能想知道编译器如何知道如何做到这一点:
int z = x + y;
如果在程序运行时没有存储任何内容以确定存储x
和y
和z
内容的内存区域是否存在,则如何知道在此处执行整数加法ints
?
简短/粗略的答案是,一旦程序运行,机器就不知道了。然而,当它生成用于运行程序的指令时,它就可以获得这些信息。
与指针的情况相同:
int intarr[] = { ... };
int *intptr = intarr;
可以在此处执行intptr + 1
之类的操作,以便将指针地址递增sizeof(int)
。编译器根据您(程序员)在此C代码中提供的信息知道这样做。如果你这样做了:
int intarr[] = { ... };
void *voidptr = intarr;
...然后尝试对voidptr
执行任何算术都会导致错误,因为我们没有提供编译器知道要生成哪些机器指令所需的信息。
我不能声明一个指向某个struct []类型数组的void指针,如果 我这样做了,void指针如何知道增量
sizeof(struct mytype)
?
它不能。 void指针等同于编译时信息的丢失,这将阻止编译器生成适当的指令。如果您不提供信息,则编译器不知道如何执行指针算法。这就是为什么接受像memcpy
这样的void指针的函数需要指定字节大小的原因。指针内容不提供那种信息,只有程序员可以提供它,因为这种信息没有存储在程序运行时使用的内存中。
答案 3 :(得分:0)
:
并在你的程序中
Output:
intptr(0xffcbf5dc): 0
intptr(0xffcbf5e0): 5
intptr(0xffcbf5e4): 10
intptr[0]: 0xffcbf5dc | intptr[1]: 0xffcbf5e0 | difference: 1 | expected: 4
如果你尝试:0xffcbf5e0 - 0xffcbf5dc = 4(hex sub),这是sizeof(int)。
答案 4 :(得分:-2)
当你将指针初始化为int *时,编译器知道它是一个int指针,每一跳应该是sizeof(int)。
关于你的无效问题*如果你这样做:
int a[4] = {0,1,2,3}
void *b = a;
int c = a[1]; //a will point to the 1 so c will be 1
int d = b[1]; //it depends on the compiler where is b going to end
//and which value will c take, some compilers may not accept it
这就是需要void *作为参数的函数,例如fwrite(),要求结构的大小和数组的大小