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

ageratum

来自: ageratum(„Zu den Sachen selbst!“)
2012-06-23 05:55:44

×
加入小组后即可参加投票
  • lichray

    lichray 2012-06-23 08:56:59

    最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印方法」的对应,不能指出一个参数的类型。再加上 format 是运行时解释的,更没有任何编译时检查(不过现在的编译器多有对这几个库函数的额外检查,但对于非常量的 format 仍然毫无办法)。结果就是,只要打印方法可用于一个变量,它懒得管这个变量原本是什么类型,也懒得管它得到是不是一个变量。 类型安全,节省运行时解释 format 的开销,这是 C++ 里 stream 库的初衷。不过,stream 的设计有它自己的问题,另一个话题了。

  • ageratum

    ageratum („Zu den Sachen selbst!“) 楼主 2012-06-23 09:33:50

    最后,我来解释一下,在这位坛友的例子中,为什么会出现“输出为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 看到这几行结果,这位坛友应当有所领悟吧!

  • ageratum

    ageratum („Zu den Sachen selbst!“) 楼主 2012-06-23 09:40:06

    最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印 最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印方法」的对应,不能指出一个参数的类型。再加上 format 是运行时解释的,更没有任何编译时检查(不过现在的编译器多有对这几个库函数的额外检查,但对于非常量的 format 仍然毫无办法)。结果就是,只要打印方法可用于一个变量,它懒得管这个变量原本是什么类型,也懒得管它得到是不是一个变量。 类型安全,节省运行时解释 format 的开销,这是 C++ 里 stream 库的初衷。不过,stream 的设计有它自己的问题,另一个话题了。 ... lichray

    您说的正在点子上。 赞——!

  • C语言猎手

    C语言猎手 2012-06-23 11:25:32

    完全不懂!

  • 薛非

    薛非 2012-06-23 11:55:03

    好帖子 好好编辑一下 简直可以直接放到《C解毒》中

  • 童话式狂躁者

    童话式狂躁者 (严肃游戏) 2012-06-24 15:57:16

    这种类型转换有什么意义?c语言的指针已经够滥用了.这只是直接说明了c语言设计的不足之处!

  • lichray

    lichray 2012-06-24 16:39:34

    这种类型转换有什么意义?c语言的指针已经够滥用了.这只是直接说明了c语言设计的不足之处! 这种类型转换有什么意义?c语言的指针已经够滥用了.这只是直接说明了c语言设计的不足之处! 童话式狂躁者

    对于 C 来说,如果不能把 void * cast 成其它类型,抽象数据类型就无从谈起。但这能说是「语言设计的不足之处」吗?范型编程始于 1983 年,而安全地实现静态类型系统的方法已经是九十年代之后的事情了。对于一个 70 年代初设计的编程语言,请给予应有的尊重。

  • 0xFAN™

    0xFAN™ (我们都是游得最快的蝌蚪) 2012-06-24 17:03:06

    话真多,C本身就是弱类型的

  • 童话式狂躁者

    童话式狂躁者 (严肃游戏) 2012-06-24 19:47:49

    我说的是将整型提升为指针类型.不是void *类型,再说我觉得void *跟数据抽象没有关系,只是与某种编程模式有关系.

  • 已注销

    已注销 (走完同一条街,回到同一个家) 2012-07-01 19:25:18

    针对上所有的所有的论述就只有一句话,代码是让人看的,机器识别的是机器码,人与人之间的代码就形成一种协议,就是这就产生了void*() 感觉很没用,可是我们也可以不用他,你可以直接给机器码,但是我们的编译软件有种约定,这就产生了语言的各种符号。要记得,我们的代码不只给机器用,有时候是需要给人看的,为了让编Coder明白,才造出来这么多符号。

  • huopo

    huopo 2012-07-02 13:54:19

    最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印 最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印方法」的对应,不能指出一个参数的类型。再加上 format 是运行时解释的,更没有任何编译时检查(不过现在的编译器多有对这几个库函数的额外检查,但对于非常量的 format 仍然毫无办法)。结果就是,只要打印方法可用于一个变量,它懒得管这个变量原本是什么类型,也懒得管它得到是不是一个变量。 类型安全,节省运行时解释 format 的开销,这是 C++ 里 stream 库的初衷。不过,stream 的设计有它自己的问题,另一个话题了。 ... lichray

    学习了,精简易懂

  • 坏人

    坏人 (阳光快来) 2012-07-02 15:20:25

    学习了

  • LinkLook

    LinkLook 2012-07-02 16:02:07

    编译时最好加上-Wall -Werror参数

  • shell-von

    shell-von (加油) 2012-07-02 21:06:17

    随便看看。深奥了

  • alu

    alu 2012-07-16 09:37:41

    学习了,有种突然明朗的感觉。

  • aguai

    aguai (此心安处便是家) 2012-07-17 03:49:22

    编译时最好加上-Wall -Werror参数 编译时最好加上-Wall -Werror参数 LinkLook

    沒錯 All Warnings should be treated as errors! 編的過不代表應該這樣寫

  • 小三三与野兽

    小三三与野兽 2012-08-22 16:56:04

    看不懂~

  • star

    star 2012-08-27 22:28:36

    略懂 不过 后面说的void *()还没有用过 有前辈热心解释的话先表感谢

  • 夏天的飞鸟

    夏天的飞鸟 2012-09-08 16:06:19

    拜读了 从某种意义上看,指针变量(指针常量不说了)和普通变量有什么区别呢,其实没什么区别 int a; int *b; 都是两个变量而已,只不过一个存储的是整型,一个存储的是地址而已。 指针只能记住内存中一段内存开头的地址,具体是什么就看你怎么解读指针了,或者是字符,或者是string,或者是数组,或者是函数。 至于头疼的地方,也就是各种转换,以及符号解读顺序,这要参照优先级及结合性。

  • Cosmia Fu

    Cosmia Fu 2012-09-13 19:43:47

    其實真相是所有的變數類型其實都是2進制整數。 還有濫用不是語言設計者的錯,而是使用者的錯,況且任意的賦值如果用的對的話,同樣可以提高效率,不管是生產效率還是執行效率。

  • 柯爺

    柯爺 (Hello,world) 2012-10-26 09:30:01

    是的,任何警告就应该等同于错误来处理。

  • [已注销]

    [已注销] 2012-11-02 10:49:57

    [内容不可见]

  • 幻の上帝

    幻の上帝 2012-12-22 12:58:47

    拜读了 从某种意义上看,指针变量(指针常量不说了)和普通变量有什么区别呢,其实没什么区别 拜读了 从某种意义上看,指针变量(指针常量不说了)和普通变量有什么区别呢,其实没什么区别 int a; int *b; 都是两个变量而已,只不过一个存储的是整型,一个存储的是地址而已。 指针只能记住内存中一段内存开头的地址,具体是什么就看你怎么解读指针了,或者是字符,或者是string,或者是数组,或者是函数。 至于头疼的地方,也就是各种转换,以及符号解读顺序,这要参照优先级及结合性。 ... 夏天的飞鸟

    在C语言讨论“变量”的概念都不靠谱。把变量和常量放在一起比较更是胡闹。

  • tingtang

    tingtang 2012-12-23 16:53:54

    把变量和常量放在一起比较更是胡闹

  • ageratum

    ageratum („Zu den Sachen selbst!“) 楼主 2012-12-28 20:48:08

    在C语言讨论“变量”的概念都不靠谱。把变量和常量放在一起比较更是胡闹。 在C语言讨论“变量”的概念都不靠谱。把变量和常量放在一起比较更是胡闹。 幻の上帝

    啊 —— 大虾你也在这里哦:) 幸会!

  • whatif5

    whatif5 2012-12-28 21:10:23

    警告视为错误

  • crazykiter

    crazykiter 2013-02-13 20:14:18

    什么呀,有这么难吗,不就是汇编里面的寻址方式吗,指针有两个属性,数据的位置和数据的操作.没了.扯多了越扯越蛋疼.

  • 慵懒的圆珠笔

    慵懒的圆珠笔 2013-03-12 19:36:16

    正在不懂。。。。。

  • 苏依

    苏依 (什么乱七八糟的) 2013-04-02 21:39:40

  • 豆友62480107

    豆友62480107 (到世界的尽头) 2013-04-28 21:44:56

    too long...

  • B_A_Cyi

    B_A_Cyi (-_-) 2013-05-11 21:41:28

    C/C++神奇的地方就在此,大神写的很好,认真的读完了,有所理解

  • 黄沙发

    黄沙发 (远离颠倒梦想,究竟涅槃。) 2013-05-11 22:05:39

    针对上所有的所有的论述就只有一句话,代码是让人看的,机器识别的是机器码,人与人之间的代码 针对上所有的所有的论述就只有一句话,代码是让人看的,机器识别的是机器码,人与人之间的代码就形成一种协议,就是这就产生了void*() 感觉很没用,可是我们也可以不用他,你可以直接给机器码,但是我们的编译软件有种约定,这就产生了语言的各种符号。要记得,我们的代码不只给机器用,有时候是需要给人看的,为了让编Coder明白,才造出来这么多符号。 ... 已注销

    +

  • 黄沙发

    黄沙发 (远离颠倒梦想,究竟涅槃。) 2013-05-11 22:06:46

    什么呀,有这么难吗,不就是汇编里面的寻址方式吗,指针有两个属性,数据的位置和数据的操作.没了.扯 什么呀,有这么难吗,不就是汇编里面的寻址方式吗,指针有两个属性,数据的位置和数据的操作.没了.扯多了越扯越蛋疼. ... crazykiter

    就是

  • 黄沙发

    黄沙发 (远离颠倒梦想,究竟涅槃。) 2013-05-11 22:07:30

    学计算机的想糊弄学电子的就难了

  • 嗷~~~

    嗷~~~ 2013-08-02 10:19:33

    想到了union,内存中数据存储的样式是已固定的,但是要看你用什么方法去读取他,不同的变量类型,会按照对应该变量的方式读取,所以虽然把P赋值给int*变量,但是printf时候,恰巧选对了读取方式(int),所以打印了对的值

  •  .

    . 2013-08-06 02:53:19

    话真多,C本身就是弱类型的 话真多,C本身就是弱类型的 0xFAN™

    c语言不是强类型吗………………

  •  .

    . 2013-08-06 02:57:51

    什么呀,有这么难吗,不就是汇编里面的寻址方式吗,指针有两个属性,数据的位置和数据的操作.没了.扯 什么呀,有这么难吗,不就是汇编里面的寻址方式吗,指针有两个属性,数据的位置和数据的操作.没了.扯多了越扯越蛋疼. ... crazykiter

    我一直也这么理解的…………不知道对不对

  • crazykiter

    crazykiter 2013-08-06 16:59:45

    c语言的概念不难,关键是把这些基本的东西用活了,可以看看开源项目源代码,才知道什么是难。

  • 青青子衿

    青青子衿 2014-01-03 15:09:20

    第一次发表评论,好紧张啊,谁能向我解释下豆瓣具体干什么的??

  • 落花生

    落花生 (迷宫中望见星空) 2014-01-03 16:02:03

    LZ,您认为写booter和驱动的人应该如何写代码来访问硬件寄存器?请举例,谢谢!

  • Wolverine

    Wolverine 2014-02-18 10:54:39

    您说的正在点子上。 赞——! 您说的正在点子上。 赞——! ageratum

    哪个点子上啊,找不到方向啊

  • Wolverine

    Wolverine 2014-02-21 18:02:56

    最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印 最大的问题在于 printf 的打印指示符。 printf 的指示符只是一个「栈上参数的长度」和「打印方法」的对应,不能指出一个参数的类型。再加上 format 是运行时解释的,更没有任何编译时检查(不过现在的编译器多有对这几个库函数的额外检查,但对于非常量的 format 仍然毫无办法)。结果就是,只要打印方法可用于一个变量,它懒得管这个变量原本是什么类型,也懒得管它得到是不是一个变量。 类型安全,节省运行时解释 format 的开销,这是 C++ 里 stream 库的初衷。不过,stream 的设计有它自己的问题,另一个话题了。 ... lichray

  • whatfor

    whatfor 2014-03-29 18:06:01

    如果您要说“指针的值,不能取常量的地址”的话,那您又错了。如下写法,就可以令指针取到常量的地址: int const a=12345; int const *pa=&a; 或 int const a=12345; int const *pa; pa=&a; 从这个角度看来,指针的用途和意义在于:获取程序中变量或常量符号实际对应于存储器的数据的位置。 【我想说,a 是【变量】 OK?】 只是这个变量是只读的,严格来说也可以修改。

  • KeyFans

    KeyFans 2014-04-22 22:57:45

    这一点代码还要解释这么长么,p也是变量,不过类型不一样罢了,32位机上一般是4字节的长度,可以存放一个32位的地址数据而已

  • 天马流星拳

    天马流星拳 2014-09-23 15:09:57

    置顶贴的水平怎么这么差。。。

  • 逍遥哥哥

    逍遥哥哥 2014-09-23 15:20:44

    扯了一堆概念,其实这些懂也好,不懂也好,对于工作生产力的提升是没有帮助的

  • 天马流星拳

    天马流星拳 2014-09-23 15:21:19

    取地址操作当然可以用于常量,常量是什么,constant variable, 既然他也是个variable,当然可以取地址。 10 为什么不能取地址,因为10是个符号symbol,它只有value,而不是variable。所以当然取不到地址。同样,ENUM类型,和MACRO也不能取地址。 int *p = 10; 只有牵强附会的意义,在很多编译器里,这句是根本通不过的,至少也会有个warning。如果要让它通过,一般是要用int *p = (int *)10, 或者在C++里,int *p = static_cast<int *> 10,但也不能保证一定通过,很可能有warning。 这句在实际编程是一定用不到的。即使在实际中需要将某段代码设置执行地址,一般发生在操作系统的启动部分,可靠的写法一般是定义宏,比如要将void sys_start() 装入内存地址0x00001000, 写法一般是定义一个宏,不定义也可以, #define LOAD_ATTR 0x00001000 然后用memcpy 将函数指针所带的函数体装入此地址。 指针就是地址,这是一个设计原则。用它装杂七杂八东西,全部应该摒弃。虽然你可以装,就好像你也可以强制往0x00000000写东西一样,也好像给了你手,你就可以用手去摸高压电一样。NEVER DO IT。

  • ageratum

    ageratum („Zu den Sachen selbst!“) 楼主 2015-05-07 04:49:30

    置顶贴的水平怎么这么差。。。 置顶贴的水平怎么这么差。。。 天马流星拳

    哈哈,我老早写的。 就像看许多自己的老帖一样,这帖我都不敢看了。我自己猜想都是:水平太差。 我继续加油吧:)

  • 猫老爷

    猫老爷 (爱吃东西 也爱黄金) 2015-07-31 13:33:00

    我就眼睁睁的看着一群学霸在这

你的回应

回应请先 , 或 注册

34574 人聚集在这个小组
↑回顶部