简单的说,使用指针的时候,*(p+1)和p[1]都是合法的。它们语义上没有区别。因此不存在*(p+1)可以p[1]不可以的情况。
在使用指针的过程中,我们需要关注的除了指针的内容外,指针是否指向有效的内存空间也是十分需要关注的。
大大的问题主要需要关注的正是指针所指内存的有效性。
大大的问题本身就不合理。这表明大大对指针的理解不够未够充分。一般开始学指针的人都有这样的问题。因此不直接回答大大所提的问题。
回答这个问题,可以从抽象的语言上的语法上的范畴来解释。但也可以从实际的,靠近机器的范畴来回答。我尝试选择后者。
先理解以下:
1. C语言有变量,变量有类型,譬如 char, int,float...,也包括指针类型
2. 不同的类型有不同的内存大小。char 1 byte, int 4 或8 byte,所有指针类型都是4或8byte
3. float标记的是浮点,int标记的是整形,而指针标记的是内存地址。
4. 指针用 *标识,而前面的类型(int *的int,char *的char, 等等)所标识的是读取方式。
5. 虽然C语言的变量是有类型的,内存本身并没有这个概念。对于内存来说,它自身只有地址和数值。
如果大大能理解这些的话。请大大回答,没有类型的内存是如何变成有类型的变量的?这估计很多初学者都难以回答。
所谓的类型,本质上是数据的意义和数据的操作的集合。
二进制码:0000 0000 0100 0001 它可以标识 字符‘null'和'A',也可以标识整形65,或者16位的颜色代码。对于字符我们可以打印,对于整形,我们可以加减乘除,对于颜色,我们可以显示。。。
因此,对于前面这块2byte内存,我们可以进行各种不一样的操作。而决定进行怎样的操作有两个关键:1,内存的抽象类型,2,类型的操作集合。
假设,我们之前的这块内存的地址是0x0001
void *p = 0x0001; 则让一个不知类型的指针指向0x0001这块内存。
int *p = 0x0001;则让一个指针指向0x0001这块内存,并告知程序此内存可以执行整形操作,譬如加减乘除,或者 void func(int i);等等。
typedef int16_t color16;
color16 *p = 0x0001;则让一个指针指向0x0001这块内存,并告知程序它可以执行与color16类型相关的操作。
如是,指针存储的是内存地址,前面的类型,则标识指针可执行的操作。
然而还有一个最很关键的问题没有讨论。那就是内存的大小。
虽然指针描述了内存的地址,但内存的有效大小是多少却没有给出。譬如,刚才的那块内存是16位。但指针并不知道它的合法长度。
事实上,每次读取指针内容的时候会按照提示的类型的大小进行读取。如果是char 则每次读取1byte。p+1或者p[1]的内容是0000 0000 (null),p+2或者p[2]的内容是0100 0001 ('A')。
如果是 int16_t或者short,每次读取2byte。p+1或者p[1]的内容是0000 0000 0100 0001。p+2或者p[2]的则语义上不合法(语法上合法),因为,超出有效内存的范围。
最后,malloc是为程序分配动态内存的方法。当调用malloc(100)的时候,系统则分配100byte给程序。这块内存直到程序结束或者使用free(void *)的时候才会被重新释放。而这100type的空间能够作为100个char的空间,或者25个32位int的空间(因此,int *p = (int *)malloc(100); p的最大合法下标是p[24]。它与int arr[100]; 是不同的)。
数组的定义,与指针之间的主要差别是:
数组:
必须有类型。给出单元的大小;
非动态分配。数组的内存是在栈里面的。当栈被pop的时候内存就被释放。也就是说,当数组所在的scope结束,数组就被释放。(所谓scope在C里面就是一对的{}括号的范围)譬如:
if(true){
int arr[10]; //arr 被分配
} //arr 被释放
指针:
可以没有类型。void的时候,算是没有类型,没有给出单元的大小,虽然void某种程度上是一种类型。
可以指向动态内存。指针所指向的未必是动态内存,但可以是动态内存。动态内存是在堆里面分配的,一般是用malloc,calloc,或者realloc,来分配。动态内存是需要用free()来释放的,静态内存一定不要用free()释放,他会自动被释放。例如
void *func(){
void *p = malloc(100);
return p; //p所指向的内存不会被释放。
}
追问我试了一下,确实有 *(p+1)就可以有p[1] 。无报错。但是只有*(p+1)时程序正常运行,如果加上p[1] 运行就会出错,内存无法read.
这就很奇怪。有*(p+1)的地方可以有p[ ] ,说可以是可以,无报错。
说不可以,也好像有道理,因为程序无法正常运行。
再说本质上,*(p+1)跟p[1]是一样的吧,但为何内存无法read呢
我又试了下int * p = (int *)malloc(40);
结果是两者都可以。无报错运行无错
追答没有报错主要是因为语法上合法。
在编程的错误有很多种,我们可以未各种错误进行分类。其中一种分类方法是,语法错误 vs 语义错误。
例如:
int a,b;
b = 0;
a = 100/b;
这段代码是可以通过编译的,所以语法上是OK的。但语义上是有错的,因为它尝试除以零。
大大的情况正是如此!
如之前所说的,使用指针的时候需要关注一个很重要的问题。那就是,你的指针所指向的内存是否合法。以下面的代码进行测试:
int *p;
int a;
a = 100;
p = &a;
printf("%d\n", *(p + 1));
printf("%d\n", p[1]);
你会发现,程序是可以编译的,但未必可以正常运行。即使可以运行,他的运行结果一般我们认为是没有意义的。
这主要是因为,p + 1或者p[1]这块内存是没有被(明显)定义的,而程序却想读取这块内存。如果这块内存在程序的内部的其他地方定义了,则可以读取,但读取出来的内容应该不是大大原来想要读取的东西(所以没有意义)。但如果这块内存它并不属于这个程序,则很有可能在运行的时候报错。另外,当指针指向不合法的内存的时候报错比不报错的好。在不报错的情况,你所执行的操作的结果是不明确的,而且这种操作很难察觉,造成的后果可以非常严重。
此外,大大的所谓p+1不报错,p[1]报错的原因有多种。其中一些可能的问题有:编译器的特性(虽然数组和指针本质上是一致的,但不同的编译器对指针和数组的处理是有微小的区别,部分编译器为了减少一些隐形错误可能对数组或这种进行额外的查错);大大对所报错误是否正确理解;代码上是否有其他地方引发错误,等等。
大大的代码运行出错的原因要具体看 大大的代码以及大大所用的编译器。
最后总结,大大所说的第一个报错,是因为,大大本来就将p指向了一个不合法的内存。
而后面说不报错,是因为这片内存是合法的。
结尾,给一个介绍C指针不错的参考材料(英文)
参考资料:http://www.cplusplus.com/doc/tutorial/pointers/