再论C语言指针、地址、赋值的问题,又是一通“扯”

时间:2022-02-28 09:15:56
再论C语言指针、地址、赋值的问题,又是一通“扯”

按:在CSDN论坛上,有位坛友提到这个问题:

引用:
先看一段代码:
#include<stdio.h>
void main()
{
  int*p=10;
  printf("%d",p);
}

看 看上述代码有什么问题没有?相信清楚指针概念的各位知道,int*p其实划分来看是(int*)p,他其实是一个指针,那么int*p=10;等价于 int*p;p=10;,大家都知道,指针就是地址,前面语句的意思是,把常量10的值赋给指针p,按照定义来说,这是不合法的,因为常量不能直接赋值给 指针,比如int a=10;int*p=&a;这才是合法的。但是我通过VC6.0编译器编译一下,文件名为al.C(注意了,非CPP后缀),结果编译器毫不报 错;输出结果为10。


看了这位坛友的帖子,实在令人担忧呀……

首先,您(称呼这位坛友)说“按照定义来说,这是不合法的,因为常量不能直接赋值给指针”。

哪里有这个“定义”呀?

我慢慢说……到后来,您就会知道,您的这种“定义”是毫无意义的。

先说啥是指针。

您说“大家都知道,指针就是地址”。这种说法是错误的(且害人的)。用这种思路去理解指针,那说明您还没有体会到,关于指针的许多真实情况。

“地址”这个观念,是为了让那些需要向存储器中的某个或某些存储单元进行数据存取的主体(比如处理器)能够找到这些存储单元,而引入的。

显然地,在这些主体看来,那些存储单元的位置(即地址),也是数据。那么,这后一种数据,也要在存储器中被存储、被读写。(从C语言编程语境来看, 这后一种数据的符号,就是指针变量或指针常量的符号。)

而“指针”这个观念的引入,与“地址”的比起来,要复杂一些,或者说,前者的用途与意义更具多样性:

(1)指针变量或指针常量的值,往往可以由一个取地址符(&)作用在一个变量或常量的符号上而获得。

如果您要说“指针的值,不能取常量的地址”的话,那您又错了。如下写法,就可以令指针取到常量的地址:

      int const a=12345;
      int const *pa=&a;


      int const a=12345;
      int const *pa;
      pa=&a;

从这个角度看来,指针的用途和意义在于:获取程序中变量或常量符号实际对应于存储器的数据的位置。

那么,对于同一个指针量,可以随程序员的意愿,在任何时候,获取任何既有的符号所对应的数据的位置,作为它的值 —— 不过,这里有一个极不可忽略的条件,下面会讲。

地址,则没有上述的意义和用途。一个符号所对应的数据,在存储器中的位置,在符号被声明的当初,就任由老Boss来分配。这个分配过程,对于程序员来说,是透明的 —— 这是高级语言与低级语言之间的一个显著区分。但由于C语言里存在着“指针”这个机制,就使得它“高级得不那么彻底” —— 因为,程序员可以透过指针,来窥探到老Boss和他的存储器情人是怎么约会的。

(2)指针量的值,除了由上面第(1)点中所说的方式获得之外,还可以由第(1)点中的方式所获得的量,再加加减减,即进行所谓“指针运算”来被赋予。让函数返回一个“实用的”存储器中的位置值,通常就是属于这一类。

(3)在第(1)点的例子里,我们已经看到:在声明指针量符号的时候,必须必须同时给出某种数据类型。这个数据类型必须必须,跟这个指针将来要指向的符号在被声明时所设置的数据类型,完全一致!

如果差那么一点儿,但尚在老Boss的理解范围之内的话,那么,老Boss会骂一句(吐出一个Warning),然后他会心不服但手服地,为指针量赋值等号右边的东西,做一些强制的转换。

但是如果差得比较离谱了,老Boss就索性罢工了。

在这位坛友的例子中:

      int*p=10;

就是属于前一种的“差那么一点儿”的情况。这时候,虽然老Boss没有罢工(没有编译error),但是您不知道,他已经有一些怨气地在暗地里,为您做了一些事情。如果您打开Warning选项,就能听到他的骂声。(千万不要以为“编译通过,程序就是写得100%合乎标准的”!)

如果您把上面的代码,改写成这样:

      int *p=(int *)10;

那么,保证老Boss不会骂您、更不会罢工。

上面这样写是什么意思呢?原来您的写法,是把一个整数10赋予了指针量p。我们姑且认为这个10就是整数常量。其实,不管这个10是个啥量啥类型,只要前面顶上一个“(int *)”,它就会被老Boss强制转换为:符号p在声明时所设置的那个类型(即指向整数类型变量的指针)。

我为什么说“不管这个10是个啥量啥类型”呢?

您看:

      int *p=(int *)'a';

就是如此,老Boss也不会骂人或罢工。

这里,“a”被括在单引号里,表达了一个字符常量,它依然可以100%合法地被赋予指针量p。

您就是写成:

      int  *p=(int *)"I Love C/C++! Oh, Yeah!";

—— 这也是100%合法的!为什么?留给这位坛友自己思考。

这一切跟“变量还是常量”没有任何关系。起决定性因素的,是类型(指针所指向的数据的类型)!

所以,您所说的“按照定义来说……常量不能直接赋值给指针”,是完全没有意义的。

数据的类型,在C语言里,是如此的重要,以至于忽略了它,整个C语言的合理性和逻辑性,就会完全丧失。

“类型”这个机制,是程序员大脑中的数据(逻辑的),与计算机内部的数据(物理的),两者之所以能够沟通的最重要媒介。一旦忽视了它,两头之间,就只剩下混乱了。

顺便说一句,这又体现了:“地址”与“指针”的不同。前者没有“类型”属性,而后者,“类型”是它的内秉属性(与生俱来的、不可或缺的)。

以上又“扯”了一通,仅供参考!

有什么错误,希望各位大虾拍砖指正!谢谢!



29 个解决方案

#1


该回复于2012-06-23 09:19:53被版主删除

#2


拜读了啊

#3


最后,来解释一下,在您的例子中,为什么会出现“输出为10”这个“奇怪的现象”。

为了解释清楚这个问题,我们把我上面所举例子的代码延伸一下:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%d\n", p);

p = (int *) 'a';

printf("%c\n", p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%s\n", p);

return 0;

}

上面的代码,运行结果为:

10
a
I Love C/C++! Oh, Yeah!

怎么样,觉得奇怪吗?

只要我们在 printf函数的第一个参数中,设定了“恰当的”format,程序就会符合我们“奸诈”的预期 —— 我们将p这个指向整数类型数据的指针(这一点自始至终都不会有变化),分别用“%d”、“%c”和“%s”这些format表示出来。而这些format又跟我们对p赋值时的那个“(int *)”右边黏着的“坏东东”的“看上去的样子”相吻合,那么,原来这些“坏东东”看上去是啥样的,现在也原样地被反馈回来。

然而,正如我在上面所强调的那样,p自始至终都是指向整数类型数据的指针,那么,它其实并不是printf函数在被设定了 “%d”、“%c”和“%s”这些format参数之后,所期盼的东东。但那又怎么办呢?我们既然这样写了,老Boss看到了之后,非常生气又无奈地把p的值(并不是p本身)分别强制转换为整数类型、字符类型、指向字符类型数据的指针。在这个过程中,老Boss连骂了三次,您在Warnings中可以看到。

请特别注意,在

      printf("%s\n", p);

中,老Boss并不是把p的值,强制转换为所谓的“字符串类型”(根本没有这种类型)。但是,为什么还是达到了我们“奸诈的预期”的结果呢?

当printf函数的第一个参数,被设定为“%s”这个format之后,它所真正期盼的,是一个指向字符类型数据的指针。程序从这个指针所指向的存储器位置开始,连续地读取数据,直到一个被约定为字符串结束标志的数据(\0)为止。

为了使我们奸诈的手段,100%地合法 —— 如同洗钱 —— 我们应当这样写:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%d\n", (int)p);

p = (int *) 'a';

printf("%c\n", (char)p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%s\n", (char*)p);

return 0;

}


这麽一来,老Boss就被蒙在鼓里,为我们老老实实地“洗钱”,再也不会骂人了。

之前我一再强调, p自始至终都是指向整数类型数据的指针,那么,在printf函数中,为了“忠实地”反馈出p原本应有的面貌,我们应当选用“%p”这个format。代码如下:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%p\n", p);

p = (int *) 'a';

printf("%p\n", p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%p\n", p);

return 0;

}


运行一下,结果是:

0xa
0x61
0x8048508

看到这几行结果,您应当有所领悟吧!

#4


原来如此

#5


膜拜啊。。。

#6


就是指针会把赋给它的值都当做是地址,这样说对么?

#7


我浅显的理解为:
定义一个指针,对指针变量 p 分别赋值 10   ‘a’   “I Love C/C++! Oh, Yeah!”10   ‘a’   “I Love C/C++! Oh, Yeah!”这些在计算机里是以二进制存储的,(关于定义  10   ‘a’   “I Love C/C++! Oh, Yeah!”时的类型声明,在我现在的理解就是给他们分配一个二进制空间存储,关于怎么存,我也不明白),再将这些二进制按你写的依次赋给变量 p(赋值的时候只传值,不改变 p 的类型) ,而读取(printf)时想要这些二进制怎么显示出来就要看你的打印的 format 和所存的变量 p 的存储 format ,如果打印的format 和存储的format 不相同,就会出现格式方面的问题了,也就是老boss开骂的一说

#8


引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

#9


引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?


应该这样说:

指针会把赋给它的值,都当做是它自己的值。

只有在程序员拿着星号、箭头以及某些“隐招”来用它的时候,指针的地址属性和作用,才呈现出来。

#10


引用 8 楼  的回复:
引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址”,说这句话有用?

这里指针存放的是这些数据的值。指针变量会把这数据当成它的值(9楼也说了)。

由此可以猜想:指针的值的存放方式和整形数据的一样,这里的值会是什么就得看你怎样用,如:用格式符%d把它当成整形等,间接取值*来把它当成地址用(但这个得到的地址很可能是无效的)。

#11


引用 10 楼  的回复:
引用 8 楼  的回复:

引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址”,说这句话有用?

……


int * p;

p = (int *) 10; //p的值不是10的地址。

p = (int *) 'a'; //p的值不是'a'的地址。

p = (int *) "I Love C/C++! Oh, Yeah!"; //p的值是该字符串的地址,但该地址作为指针量的值的时候,这个指针(等号两侧的每一个均)是指向整数类型数据的。


#12


引用 11 楼  的回复:
引用 10 楼  的回复:

引用 8 楼  的回复:

引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C……


不晓得你想表达什么。

#13


理解很深刻……

#14


引用 11 楼  的回复:
引用 10 楼 的回复:

引用 8 楼 的回复:

引用 6 楼 的回复:
p = (int *) "I Love C/C++! Oh, Yeah!"; //p的值是该字符串的地址,但该地址作为指针量的值的时候,这个指针(等号两侧的每一个均)是指向整数类型数据的。

这里居然赋的是地址,可以理解为是因为 "" 的作用么? 将  I Love C/C++! Oh, Yeah  存储,运算时引用其地址

#15


指针是存储地址的变量。。。

指针是变量。。。

#16


拜读了.....

#17


引用 15 楼  的回复:
指针是存储地址的变量。。。

指针是变量。。。

NO NO...

#18


拜读了

#19


我的看法:
1 ,赋值操作能通过主要是俩边的类型一样,,即使不一样也可以强制转换变成一样的可以通过
2  ,对于int *p 是否可以这样理解 p是一个地址。。就像邮局上的地址一样。。而*p的*号就像送快递的 根据这个地址 找到 地址所在然后读出所在内容

#20


大神啊!

#21


引用 19 楼  的回复:
我的看法:
1 ,赋值操作能通过主要是俩边的类型一样,,即使不一样也可以强制转换变成一样的可以通过
2  ,对于int *p 是否可以这样理解 p是一个地址。。就像邮局上的地址一样。。而*p的*号就像送快递的 根据这个地址 找到 地址所在然后读出所在内容


您的看法很有道理:)

只要不引起歧义,利用这个思路来理解程序,基本上还是可以的。

呵呵……

#22


我也看到那个帖子了,现在明白了,学习利,3Q FOR LZ

#23


这就是传说中的牛人吗?受教了~

#24


眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!

#25


果然,指针跟类型有关,跟存入的内容无关(只要编译器认识,不认识就强转)

#26


赞,受教了!

#27


CSDN服务器的时钟又一次被来自太阳的γ射线击穿!
我为什么要说“又”? 再论C语言指针、地址、赋值的问题,又是一通“扯”

#28


赞一个。。。

#29


z赞!通俗易懂

#1


该回复于2012-06-23 09:19:53被版主删除

#2


拜读了啊

#3


最后,来解释一下,在您的例子中,为什么会出现“输出为10”这个“奇怪的现象”。

为了解释清楚这个问题,我们把我上面所举例子的代码延伸一下:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%d\n", p);

p = (int *) 'a';

printf("%c\n", p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%s\n", p);

return 0;

}

上面的代码,运行结果为:

10
a
I Love C/C++! Oh, Yeah!

怎么样,觉得奇怪吗?

只要我们在 printf函数的第一个参数中,设定了“恰当的”format,程序就会符合我们“奸诈”的预期 —— 我们将p这个指向整数类型数据的指针(这一点自始至终都不会有变化),分别用“%d”、“%c”和“%s”这些format表示出来。而这些format又跟我们对p赋值时的那个“(int *)”右边黏着的“坏东东”的“看上去的样子”相吻合,那么,原来这些“坏东东”看上去是啥样的,现在也原样地被反馈回来。

然而,正如我在上面所强调的那样,p自始至终都是指向整数类型数据的指针,那么,它其实并不是printf函数在被设定了 “%d”、“%c”和“%s”这些format参数之后,所期盼的东东。但那又怎么办呢?我们既然这样写了,老Boss看到了之后,非常生气又无奈地把p的值(并不是p本身)分别强制转换为整数类型、字符类型、指向字符类型数据的指针。在这个过程中,老Boss连骂了三次,您在Warnings中可以看到。

请特别注意,在

      printf("%s\n", p);

中,老Boss并不是把p的值,强制转换为所谓的“字符串类型”(根本没有这种类型)。但是,为什么还是达到了我们“奸诈的预期”的结果呢?

当printf函数的第一个参数,被设定为“%s”这个format之后,它所真正期盼的,是一个指向字符类型数据的指针。程序从这个指针所指向的存储器位置开始,连续地读取数据,直到一个被约定为字符串结束标志的数据(\0)为止。

为了使我们奸诈的手段,100%地合法 —— 如同洗钱 —— 我们应当这样写:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%d\n", (int)p);

p = (int *) 'a';

printf("%c\n", (char)p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%s\n", (char*)p);

return 0;

}


这麽一来,老Boss就被蒙在鼓里,为我们老老实实地“洗钱”,再也不会骂人了。

之前我一再强调, p自始至终都是指向整数类型数据的指针,那么,在printf函数中,为了“忠实地”反馈出p原本应有的面貌,我们应当选用“%p”这个format。代码如下:

#include<stdio.h>

int main(void) {

int *p;

p = (int *) 10;

printf("%p\n", p);

p = (int *) 'a';

printf("%p\n", p);

p = (int *) "I Love C/C++! Oh, Yeah!";

printf("%p\n", p);

return 0;

}


运行一下,结果是:

0xa
0x61
0x8048508

看到这几行结果,您应当有所领悟吧!

#4


原来如此

#5


膜拜啊。。。

#6


就是指针会把赋给它的值都当做是地址,这样说对么?

#7


我浅显的理解为:
定义一个指针,对指针变量 p 分别赋值 10   ‘a’   “I Love C/C++! Oh, Yeah!”10   ‘a’   “I Love C/C++! Oh, Yeah!”这些在计算机里是以二进制存储的,(关于定义  10   ‘a’   “I Love C/C++! Oh, Yeah!”时的类型声明,在我现在的理解就是给他们分配一个二进制空间存储,关于怎么存,我也不明白),再将这些二进制按你写的依次赋给变量 p(赋值的时候只传值,不改变 p 的类型) ,而读取(printf)时想要这些二进制怎么显示出来就要看你的打印的 format 和所存的变量 p 的存储 format ,如果打印的format 和存储的format 不相同,就会出现格式方面的问题了,也就是老boss开骂的一说

#8


引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

#9


引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?


应该这样说:

指针会把赋给它的值,都当做是它自己的值。

只有在程序员拿着星号、箭头以及某些“隐招”来用它的时候,指针的地址属性和作用,才呈现出来。

#10


引用 8 楼  的回复:
引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址”,说这句话有用?

这里指针存放的是这些数据的值。指针变量会把这数据当成它的值(9楼也说了)。

由此可以猜想:指针的值的存放方式和整形数据的一样,这里的值会是什么就得看你怎样用,如:用格式符%d把它当成整形等,间接取值*来把它当成地址用(但这个得到的地址很可能是无效的)。

#11


引用 10 楼  的回复:
引用 8 楼  的回复:

引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址”,说这句话有用?

……


int * p;

p = (int *) 10; //p的值不是10的地址。

p = (int *) 'a'; //p的值不是'a'的地址。

p = (int *) "I Love C/C++! Oh, Yeah!"; //p的值是该字符串的地址,但该地址作为指针量的值的时候,这个指针(等号两侧的每一个均)是指向整数类型数据的。


#12


引用 11 楼  的回复:
引用 10 楼  的回复:

引用 8 楼  的回复:

引用 6 楼  的回复:
就是指针会把赋给它的值都当做是地址,这样说对么?

不能这样说
指针变量和普通变量没什么区别的,只是在我们用的时候一般存的是一个地址,但在这个帖子里的指针变量存的就不是地址(不是10 ‘a’ “I Love C/C++! Oh, Yeah!”的地址 )

“不是10 ‘a’ “I Love C……


不晓得你想表达什么。

#13


理解很深刻……

#14


引用 11 楼  的回复:
引用 10 楼 的回复:

引用 8 楼 的回复:

引用 6 楼 的回复:
p = (int *) "I Love C/C++! Oh, Yeah!"; //p的值是该字符串的地址,但该地址作为指针量的值的时候,这个指针(等号两侧的每一个均)是指向整数类型数据的。

这里居然赋的是地址,可以理解为是因为 "" 的作用么? 将  I Love C/C++! Oh, Yeah  存储,运算时引用其地址

#15


指针是存储地址的变量。。。

指针是变量。。。

#16


拜读了.....

#17


引用 15 楼  的回复:
指针是存储地址的变量。。。

指针是变量。。。

NO NO...

#18


拜读了

#19


我的看法:
1 ,赋值操作能通过主要是俩边的类型一样,,即使不一样也可以强制转换变成一样的可以通过
2  ,对于int *p 是否可以这样理解 p是一个地址。。就像邮局上的地址一样。。而*p的*号就像送快递的 根据这个地址 找到 地址所在然后读出所在内容

#20


大神啊!

#21


引用 19 楼  的回复:
我的看法:
1 ,赋值操作能通过主要是俩边的类型一样,,即使不一样也可以强制转换变成一样的可以通过
2  ,对于int *p 是否可以这样理解 p是一个地址。。就像邮局上的地址一样。。而*p的*号就像送快递的 根据这个地址 找到 地址所在然后读出所在内容


您的看法很有道理:)

只要不引起歧义,利用这个思路来理解程序,基本上还是可以的。

呵呵……

#22


我也看到那个帖子了,现在明白了,学习利,3Q FOR LZ

#23


这就是传说中的牛人吗?受教了~

#24


眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!

#25


果然,指针跟类型有关,跟存入的内容无关(只要编译器认识,不认识就强转)

#26


赞,受教了!

#27


CSDN服务器的时钟又一次被来自太阳的γ射线击穿!
我为什么要说“又”? 再论C语言指针、地址、赋值的问题,又是一通“扯”

#28


赞一个。。。

#29


z赞!通俗易懂