October 2018

指向二维数组的指针 以及一些 指针地址相减的问题

前面我们讲到了指向变量的指针和指向一维数组的指针,这篇文章我们来讨论一下指向二维数组的指针。

对于二维数组指针,需要注意的是对于不同情况下的下标变化,指针保存的地址值增加的长度(步长)是不同的,以下我们看一下不同情况下指针步长有什么规律(部分内容可能涉及简单的汇编来帮助理解)。

我们先看以下代码:

编译运行结果:

接下来我们一点一点的分析为什么是这些结果。

首先我们定义并初始化了一个二维数组,它在内存中可以表示为如下形式:

这里我们假设的是数组起始地址值为0x10(实际地址虽然也打印出来了,不过太长就不用了),其中蓝色的是数据,黑色的是数据所在的内存地址,紫色是数组的行号和列号,即下标。(其实它们在内存上是线性排列的,这样画是为了便于理解)

接着我们定义了一个指向该二维数组的指针p,并将num的值(0x10)赋值给了它,现在p=0x10。

对于上述代码,可以看成这样 int[3]  *p; 也就是我们定义了一个*p, 指向的是包含3个int型元素的数组。因为*操作是从右向左结合的,而小括号优先级最高,所以*p会结合成一对,这样理所当然的int和[3]结合在一起了。

扩展一下,如果我们定义成如下形式又表示什么意思呢?

根据前面提到的结合方向,我们可以得出上述代码可以理解成int* p[3];也就是我们定义了一个数组,且该数组有3个元素,元素类型为int*,即数组里的每个元素都是指向int型的指针,这个我会在后面指针剩余部分提到,那就是另一篇文章了,也就是所谓的指针数组了。

点到为止,我们继续分析一下原来的代码。

假设p的地址为0x30,此时的数据分布可以用下图表示:

可以看到,num是一个地址,值为0x10,它即是作为第一行三个int型数据数组的首地址,也是整个二维数组的首地址。

以上代码中的p[1],会首先获得指针p里面存的内容0x10,然后在根据下标获得真正目标的地址值,具体这个下标1表示的步长究竟是多少呢?我们再看一下定义时候的代码:

由上面的讨论我们知道,我们定义的是一个指针*p,该指针指向了一个包含3个int型变量的数组。即,我们每次对p加1,实际需要跨过的地址便是1*sizeof(int[3])=4*3=12字节了,所以p[1]会和数组的首地址相差12个字节。

对应会实际代码的运行结果,我们算一下到底是不是这样呢?
num = 0x7ffc54dc4920
p[1] = 0x7ffc54dc492c

则可得出差值 = 0x7ffc54dc4920 – 0x7ffc54dc492c = 0xc,转化成10进制的话,就是12了,与我们推测计算的一样!

承接上面的结论,既然p[1]是num的地址基础上再跨越了3个int型数据,那么它的地址就是第4个int型数据的地址了,即使下图的第4个元素,值为4的地址,再取*操作,就得到了该地址下面的值为4了。

而这句为什么打印的不是4呢?

因为我们加了一个小括号,使指针p和*号先结合起来了,这就会先完成*p操作,获得num的首地址0x10,又因为数组num是int型数据的,所以会再根据下标1,完成了地址偏移量计算1*sizeof(int)=4,再计算出最终目标地址是0x10+0x04=0x14,这时就有点像数组索引了,因为*p就是数组的首地址,再加一个下标值,便会取出对应地址0x14的数据了,所以得到的结果便是(*p)[1])=2了。

再看一下最后一句:

按照上面一条讲的那样,*p取得了num的地址,再计算出地址偏移量=5*sizeof(int)=20(即0x14);那么目标地址即是:0x10+0x14=0x24,对应的值就是6了。

我们再推测一下,如果是num[5]是不是得到一样的结果呢?

毕竟刚刚*p可就是num的值啊。转化一下不就是(*p)[5] = num[5]吗?

其实,不是这样的,数组和指针还是有区别的,毕竟它们本身就是不同的数据类型,只是在某些情况下有一些互通的地方,加之数组名代表了数组的一个地址,不可以给它赋值,而指针是可以的,其次,当用sizeof(num)和sizeof(p) 时得到的分别是数组的大小(而不是num代表的地址值的数据大小)和指针的大小(8字节,占用64位)。

所以如果是num[5]的话,那么第一个下标指的是行,而每行是3个int型元素,下标每加1,代表跨过了一整行,那就是sizeof(int[3])=12字节了,num[5]相比于首地址偏移地址量就为12*5=60了。

而(*p)[5]是取得了首地址num,而num是int型数组,每个元素占用sizeof(int)=4字节,所以偏移地址量就是4*5=20了,相比于num[5]的60,实在相差甚远。

为了验证上面的的推论,我们再写一段代码,打印出num+60的值和&num[5]的值进行比较,如果相等就说明推论正确,如果不相等,我们就得再推敲推敲了。

Continue reading...

指针

指针其实也是一个变量,只是这个变量比较特殊,专门存放其他变量在内存上的位置值(叫做地址值)。

就像一本书上的目录,它不包含每篇文章的内容,但它携带了对应文章的页码,比如我们要查看哪一篇文章在哪一页,去看一下目录找到对应的文章标题,它的末尾就写明了这篇文章在哪一页,然后再翻到那一页,我们就能读取文章内容了。

我们定义一个指针,让它指向一个变量,再把他打印出来:

运行结果:

我们来详细讲解一下,指针是怎么运作的。

首先我们定义了一个int型的变量 num, 并赋初值26. 代码运行后便会在内存的某个地址中初始化这个变量,假设该地址在0x1a处。如下图:

当我们执行如下操作:

&num会获得num在内存中的地址0x1a, 再赋值给指针变量p,之后p=0x1a,假设p变量被放在内存中的0x20处,那么指针变量的示意图可以表示为如下:

再执行printf打印*p时, 实际的操作过程是取得p的值(0x1a), 再去找到内存0x1a处,获取里面的数据26,再调用printf打印出这个值。

那么如果我们只打印p会得到什么呢?

我们继续看如下代码:

运行结果(注意每次运行的结果可能不同,因为开辟的内存地址可能不同):

其中%p表示目标以地址的形式输出。

我们可以看到,p直接以%d的格式输出的话,值为一个负数-818621124
,那么它与0x7ffecf34d53c有什么关系呢?

其实,-818621124是0x7ffecf34d53c的二进制的低32位转化成10进制后的值,-818621124在计算机中是以补码的形式存在的,其为负数,表示最高位(符号位,正号为0,负号为1)为1。

因为 源码的补码=源码的反码+1;

所谓反码就是在二进制的情况下,除了符号位,对每一位取反。

知道了上面的规律,我们就可以反推出-818621124在计算机中存储时候的二进制情况了。

先计算一下-818621124的二进制表示形式:

再看一下0x7ffecf34d53c的二进制形式:

对比-818621124的补码和0x7ffecf34d53c的二进制有没有发现什么?

没错-818621124的32位二进制补码和0x7ffecf34d53c的低32位完全相同。

但指针是8字节的,8*8=64位,那么问题又来了,为什么是指针值的低32位才相同,而不是64位呢?

那是因为int型是4字节,即4*8=32位,而我们打印出p的时候,用的是%d对应的就是int型,所以它才会把p的值的低32位转化成10进制给打印出来,当然就不是取64位转化打印出来了。

END~…

Continue reading...

一维数组指针

所谓数组指针,即是指向数组的指针。指针变量用来存放数组的地址值,通过星号操作 ‘ * ‘ 来间接访问该地址下的数据。

如我们定义一个int型数组:

那么便会在内存中(栈)开辟一个地址连续的空间,用来存放这三个数据,我们假设起始地址为0xff0,那么这三个数在内存中会按如下排列,其中地址每次加4,是因为一个int型数据占用了四个字节。

我们再定义一个数组指针,用来指向这个数组:

因为数组本质上就是通过地址索引的,如上面的图示中,如果要读取出第二个数据num[1],那么从汇编上来看,需要知道这个数组的首地址0xff0,再根据下标[1],计算出它的真正地址:0xff0+1*sizeof(int) = 0xff0 + 4 = 0xff4,再取目标地址0xff4里面的数据,即为2。

而在C语言中,数组名就代表了数组的首地址,也就是说在本例中,num即代表了0xff0。

我们用 int *p = num这个操作,将num的地址0xff0赋值给p,而p也是一个变量,它也需要存放在内存中,而内存是需要地址去访问的,所以p在内存中也是有一个自己的地址的。p内部存放的数据就是刚刚赋值的0xff0,假设p在内存地址为0xfe8处,那么用图的形式表示为:

由上图我们可以看到p的值就是数组的首地址值0xff0,如果我们要访问数组的3个值,就可以不用直接操作数组了,可以通过如下方式获得:

因为num即代表了数组的首地址,而num的值(0xff0)赋值给了p,所以num[1]和p[1]两者表达的意思是一样的。

对于*(p+1)这样的操作为什么也可以呢?是因为p+1操作对应的汇编上,相当于这样一段指令(当然以下不是真的汇编指令,而是写成了C格式的汇编操作过程):

一番操作后我们就得到了数组第二个数的地址值eax=0xff4了,此时*(p+1) 就等价于 *eax, 星 * 操作后即获得地址值0xff4对应的内容2。

代码示例:

运行结果:

Continue reading...

Leetcode 第一刷纪念笔记

一直觉得C语言学得还不错,且早已听说Leetcode的大名,今天悻悻地去leetcode的官网刷了第一道题 —- Circular Queue

做完后暴露了好多问题。

其题目要求如下(点击图片放大):

系统隐藏了main函数的部分,只给出了要求使用和补充完善的函数(点击图片放大):

乍一看,一脸懵逼。我结构题哪里定义?这也没给我传进第一个初始化函数啊?

不得不说,这里让我纠结了许久,脑子转不过来,后来突然想到,根据最后的注释提示,应该是初始化函数 myCircularQueueCreate(int k) 这里,申请了一个内存空间时创建了结构体,然后函数返回时并不会清理掉这部分内存,当初写C语言终端离线词典时自己就是这么干的,只要把这部分内存首地址返回给调用函数就可以了。

另外,不能在函数中不申请内存直接初始化一个结构体,再返回结构体的地址。如果这样的话,函数返回时这些数据都会被释放掉,返回的地址也就没用了,如果这个时候再操作这个地址指向的内存空间,将会发生Segmentation fault。

还有一个问题需要注意,如果申请了一段内存空间,并且访问了该内存空间之外的地址(如申请内存中的数组越界),当下可能不会报错,为什么说是可能呢?因为一般这种情况下指针跑飞到的地方,访问的都是不影响系统运行的位置,如果不幸撞到了枪口,那就只能立即‘挂机’了。

接着上面的,当下虽然没有报错宕机,但是当调用free函数释放该段内存时,程序就会抛出double free or corruption (out)的错误了。

为什么会这样呢?因为当我们申请内存空间时,实际用到的内存会比我们申请的大一点,这个额外的空间就是用来记录这部分的长度以及下一个分配块的地址等等的,如果发生了越界,就会改写到这部分信息,最终调用free时会检测到异常而抛出错误。

至于这部分额外空间在哪呢,在我的电脑上检测后,发现,应该在内存前后都有一些关键信息,无论是向前还是向后越界,free都会发生错误,只不过报错信息不同,说明其前后存的信息是不同的(废话了,相同还存什么,手动捂脸。另:这部分的具体原因,等我查阅好相关文档后再来填坑。)

这题虽然不难,自己思路也很清晰,但问题就在于手跟不上大脑,打出来的跟想的还不一样,细节上被自己坑了很多次,以为想的是对的,打出来后就一样了,一眼扫过也没发现有些代码顺序在修改的时候已经不小心调换了,这就造成了代码上致命的逻辑错误了。

最后奉上代码被Accepted的截图吧(点击图片放大):

Continue reading...

你是无意穿堂风,偏偏孤倨引山洪

短语

· 你是无意穿堂风,偏偏孤倨引山洪。

· 我怀里所有温暖的空气,变成风也不敢和你相遇。

· 你要是愿意,我就永远爱你。你要是不愿意,我就永远相思。

· 所爱隔山海,山海不可平。

· 每个人都有一个死角,自己走不出来,别人也闯不进去,我把最深沉的秘密放在那里,你不懂我,我不怪你,只是,你若懂我,该有多好。

· 一直以来我都以为人与人最远的距离,是我站在你面前,你却不知道我爱你,却忘了其实最远的距离是,你我多年未见,我念你如初,你却笑问我名。

· 我的整个一生都是属于你的,而你,却始终对我一无所知。

· 当一个人不能拥有的时候,他唯一能做的便是不要忘记。

· 有些人只拥吻影子,于是只拥有幸福的幻影。

· 人生嘛,总有事与愿违。何妨遗憾当唯美,狼狈作点缀,静水深流亦不悔。

· 你在婚礼上,使用红筷子。我在向阳坡,栽下两行竹。

· 想人想的厉害的时候,也是淡淡的。像饿了许多日的人闻到炊烟,但知道不是自家的。

· 雾起潮生,月明星稀,那时你在远处走,我在后面偷偷地望着你。

· 有时候我会很想你,对着风声说我爱你。

· 有些爱,只能止于唇齿,掩于岁月。

· 有时候我会想起你,你应该是在北方。

· 你我之间本无缘分,全靠我死撑。我明白的。

· 雨声潺潺,像住在溪边,宁愿天天下雨,以为你是因为下雨不来。

· 见不到你的日子里,我写了很多永远寄不出去的信。总觉得写下来,你就会知道,总觉得我在想念,你就会明了。

· 你不会知道吧,我一天至少十六个小时都在想你。真是可惜,不能保证的是天天都能梦到你。

· 就那样端坐在那里吧,你可千万不要突然回头啊。

· 的确,我爱你,却没让你体会到,这实在是我的过错。

· 你走我不会去送你,你来,不管风雨再大,我也会去接你。

· 当她检完票,我开始羡慕那节火车。

· 后来我爱的人都是你的模样。

· 你是我少年时期的风和长大了的梦。

· 风吹散了云,你绕开了我。

· 暗恋这件事,我本不想有发言权,可是遇见了你,成了我这辈子最擅长的事。

· 暗恋是一个人的兵荒马乱,你可不可以给个机会,让我避免这场灾难。

· 海底月是天上月,眼前人是心上人。

· 你是我穷极一生也没做完的梦,而我是你一念之间就吹过的风。

· 毕竟这穿梭山河的箭,刺的都是思念成疾的人。

· 黄昏时偷来你的肋骨酿酒,百年后醉得有血有肉。

· 是大梦初醒,不是你。

· 一眼就心动的人,要怎么做朋友。

· 世界上最幸福的事,就是和一个不庸俗的人,做一对庸俗的情侣。

· 暗恋成了一种习惯,卑微已经根植在了骨子里,刮骨疗毒都抹不干净。

· 相比你众叛亲离与我相依为命,我更希望你得天独厚,应有尽有,被全世界喜爱,哪怕彼此相忘于江湖。

· 人真正应该做的,是对自己的感觉和情绪忠诚。你怎样想,怎样感觉,就怎样选择。成功失败,得到失去,这都是选择之后的结果,却不应该是选择时候的原因。

· 当时的他是最好的他,后来的我是最好的我。可是最好的我们之间,隔了一整个青春。怎么奔跑也跨不过的青春,只好伸出手道别。

· 一厢情愿,就得愿赌服输。

· 青春就是这样,好得像是无论怎样度过都会被浪费。那么,不如浪费在你身上。

· 我不幸是世界上最不快乐的那种人,没能力,却有上进心;没天赋,却有梦想;越努力,越难过。

· 不是所有坚持都有结果,但是总有一些坚持,能从一寸冰封的土地里,培育出十万朵怒放的蓄薇。

· 蟹在剥我的壳,笔记本在写我,漫天的我落在枫叶上雪花上,而你在想我

· 他的记忆里没有我,我的一整个青春却全部都是他。

· 有人认为爱是性,是婚姻,是清晨六点的吻,是一堆孩子,也许真是这样的,莱斯特小姐。但你知道我怎么想吗,我觉得爱是想触碰又收回手。

· 如果当初我勇敢,结局是不是不一样。如果当时你坚持,回忆会不会不一般。最终我还是没说,你还是忽略。

· 虽然经历了岁月的洗礼 但真挚的感悟没有磨灭 生命是短暂的 而爱情是永恒的。 有一个可以思念的人就是幸福。

· 十二月的阳光下,我正偷看你的侧脸,你的身影犹如荡漾在春风中的一首歌,你一定全都知道,你一定全都不在乎,就这样回过头,清凉的一笑。

· 高考结束了 你也毕业了 最后一天我看着你搬书 和朋友聊天 用手机偷偷拍下你的背影 好像偷偷喜欢了你一年也该有个结局了 最后一天我看着你你看着我 也许过了那天你就不记得我了 但也挺开心的 多幸运啊遇见你 微风轻轻起 我好喜欢你。

· 相恋的人分开了,受伤的会是两个人,而暗恋不会,那是一个人的事,你一定不会受伤。

诗句、诗

  • 似此星辰非昨夜,为谁风露立中宵。——黄景仁《绮怀》
  • 平生不会相思,才会相思,便害相思。空一缕余香在此,盼千金游子何之。——徐再思《折桂令春情》
  • 无情不似多情苦,一寸还成千万缕。天涯地角有穷时,只有相思无尽处。——晏殊《玉楼春春恨》
  • 夜月一帘幽梦,春风十里柔情。——秦观《八六子倚危楼》
  • 天不老,情难绝。心似双丝网,中有干千结。夜过也,东窗未白凝残月(孤灯灭)。——张先《千秋岁·数声悲鹅》
  • 心几烦而不绝兮,得知王子。山有木兮木有枝,心悦君兮君不知。——《越人歌》
  • 海水梦悠悠,君愁我亦愁。南风知我意,吹梦到西洲。——南北朝《西洲曲》
  • 直道相思了无益,未妨调怅是清狂。——李商隐《无题》
  • 相思了无益,悔当初相见。——朱舞尊《忆少年·飞花时节》
  • 曾与美人桥上别,恨无消息到今朝。——刘禹锡《杨枝词》
  • 争忍不相寻?怨孤念。换我心,为你心,始知相忆深。——顾复《诉衷情·永夜抛人何处去》
  • 落花人独立,微雨燕双飞。——晏几道《临江仙梦后楼台高锁》
  • 欲把相思说似谁,浅情人不知。——晏几道《长相思长相思》
  • 章台柳,章台柳,昔日青青今在否?——韩栩《章台柳寄柳氏》
  • 若教眼底无离恨,不信人间有白头。肠已断,泪难收。相思重上小红楼。——辛弃疾的《朗鸪天·晚日寒鸦一片愁》
  • 红楼隔雨相望冷,珠箔飘灯独自归。——李商隐《春雨》
  • 漠漠轻寒上小楼。晓阴无赖似穷秋。淡烟流水画屏幽。自在飞花轻似梦,无边丝雨细如愁。宝帘闲挂小银钩。——秦观《浣溪沙》
  • 借问江潮与海水,何似君情与妾心?相恨不如潮有信,相思始觉海非深。——白居易《浪淘沙》
  • 前尘往事断肠诗,依为君痴君不知。莫道世界真意少,自古人间多情痴。——苏轼《无题》
  • 斑雅只系垂杨岸,何处西南待好风。——李商隐《无题》
  • 尊前拟把归期说,未语春容先惨咽。—欧阳修《玉楼春》
  • 明月不谙离恨苦,斜光到晓穿朱户。——晏殊《蝶恋花·槛菊愁烟兰泣露》
  • 相思似海深,旧事如天远。泪滴干千万万行,更使人愁肠断。要见无因见,了拼终难拼。若是前生未有缘,待重结、来生愿。——乐婉《卜算子·答施》
  • 折花枝,恨花枝,准拟花开人共后,开时人去时。怕相思,已相思,轮到相思没处辞,眉间露一丝。——前彦《长相思·折花枝》
  • 满城烟水月微茫,人倚兰舟唱。常记相逢若耶上,隔三湘,碧云里断空惆怅。美人笑道,莲花相似,情短藕丝长。——杨果《小桃红·满城烟水月微茫》
  • 寻好梦,梦难成。况谁知我此时情。枕前泪共帘前雨,隔个窗儿滴到明。——聂胜琼《膀鸪天》
  • 美人卷珠帘,深坐蛾眉。——李白《怨情》
  • 一看肠一断,好去莫回头。——白居易《南浦别》
  • 离愁渐远渐无穷,迢迢不断如春水。——欧阳修《踏莎行》
  • 君若扬路尘,妾若浊水泥,浮沈各异势,会合何时谐?——曹植《明月上高楼》
  • 而今往事难重省,归梦绕秦楼。相思只在:丁雪枝头,豆蔻梢头。——王秀《眼儿媚》
  • 他生莫作有情痴,人间无地著相思。——况周颐《减字浣溪沙》
  • 一埠沧海变桑田,一埠留君作思念。——《梅枝双雀图》
  • 梧桐树,三更雨,不道离情正苦。一叶叶,一声声,空阶滴到明。——温庭《更漏子三首其三》
  • 凄凉别后两应同,最是不胜清怨月明中。——纳兰性德《虞美人·曲阑深处重相见》
  • 盈盈一水间,脉脉不得语。——《迢迢牵牛星》
  • 临别殷勤重寄词,词中有誓两心知。——白居易《长恨歌》
  • 直缘感君恩爱一回顾,使我双泪长珊珊。——卢全《楼上女儿曲》
  • 泪眼问花花不语,乱红飞过秋千去。——欧阳修《蝶恋花庭院深深深几许》。
  • 相思本是无凭语,莫向花榄费泪行。——婴几道《鹏鸪天三首其二》
  • 嗟余只影系人间,如何同生不同死?——陈衡恪《题春绮遗像》
  • 忆君心似西江水,日夜东流无歌时。——鱼玄机《江陵愁望寄子安》
  • 山月不知心里事,水风空落眼前花,摇曳碧云斜。——温庭药《梦江南千万恨》
  • 相思一夜情多少,地角天涯未是长。——张仲素《燕子楼》
  • 人如风后入江云,情似雨徐黏地絮。——周邦彦《玉楼春》
  • 情人怨遥夜,竟夕起相思。——张九龄《望月怀远》
  • 鱼沈雁查天涯路,始信人间别离苦。——戴叔伦《相思曲》
  • 还卿一钵无情泪,恨不相逢未剃时。——苏曼殊《本事诗》
  • 若有知音见采,不辞遍唱阳春。——晏殊《山亭柳·赠歌者》
  • 莫唱当年长恨歌,人间亦自有银河。——袁枚《马》
  • 深知身在情长在,怅望江头江水声。——李商隐《暮秋独游曲江》
  • 可惜明年花更好,知与谁同?——欧阳修《浪润沙把酒祝东风》
  • 终日两相思,为君憔悴尽,百花时。——温庭药《南歌子四首其二》
  • 泪纵能乾终有迹,语多难寄反无词。——陈端生《寄外》

摘取自知乎:有哪些关于暗恋的诗句、短文或书籍、音乐、电影? – 对的人黄尔强的回答

Continue reading...

网站复活通知

 

前些日子网站被黑了,数据库也被删除了个干净,然而并没有备份,懒得打理废置了好一段时间。

昨天兴起重新弄了回去,无奈网上的博客教程之类的坑太多,走了一些弯路,今天终于满血复活了,顺便开启了https访问。

哈哈,新生活开始了,加油吧。

 …

Continue reading...