记录一个比较基础的东东…… C 语言的指针,一直让人又爱又恨,爱它的人觉得它既灵活又强大,恨它的人觉得它太过于灵活太过于强大以至于容易将人绕晕。最早接触 C 语言,还是在刚进入大学的时候,算起来有好些年头了;我当年做过的一个最糟糕的决定(也是如今回想起来依然觉得很 2B 的决定)也和 C 语言有关(和本文主题无关,略去不表)…… 由此说来,和 C 的缘分还是蛮重的。可惜,今天,我还是在一个关于指针的问题上,小小迷糊了一下…… 曾经还自诩熟读《The C programming language》,真惭愧……

两个案例。

1. 拿今天碰到的实际例子来展开讲。

对于静态类型的语言,「类型」的概念是根深蒂固、深入到原语操作级别的。如 int i = 5, j = 6; j = i; 里的第二条语句,就是将 i 所表征的一块内存区域的内容,拷贝到 j 所表征的一块同样大小的内存区域里去,而内存区域的大小,则是 int 类型的 in-memory storage size(固定长度),即编译期就完成的 sizeof (data_type) 操作结果。正是这种「面向机器的最浅抽象」,使得 C 语言变得很强大;你可以很清楚的了解,某个变量的长度是多少,因为其类型是固定的,而「变量名」本身,则不过是你希望记住的某 memory block 的「别名」。

切入正题。如下三行代码(隐去了数据结构的具体名称等细节),p 是新定义的指针(指向结构体类型 T),data_buffer 则是一种 void * 型内存块,其中存储的,实则是由指向结构体 T 的指针构成的「指针数组」。要做的很简单,就是将位置在第 i 处的指针,拷贝给新定义的 p 指针。听起来很简单,对吗?

struct T *p = nullptr;
p = (struct T *) ((char *)data_buffer + elem_size * i); // code1
memcpy(&p, (char *)data_buffer + elem_size * i, sizeof(struct T *)); // code2
p = ((struct T **)data_buffer)[i]; // code3

ok,虽然这事儿不说也没人知道,但哥还是决定自黑一下!=_=

首先,我随手就写上了第二行代码(简称 code1 …)。
唔,这是值得被鄙视的一行代码,更值得被鄙视的,是哥第一时间 code review 时并没有发现其中的问题,debug 时才突然意识到掉进了这么经典的坑里,后悔不迭(就差面红耳赤了)…… 问题在哪儿呢?

指针本身只是一个长度为 sizeof (void *) 的区域里存储的 value,而 value 本身则是另一个变量的地址;使用指针类型的 explicit conversion(强制类型转换)并赋值,如「p = (struct T *) q」,其含义,是指 q 本身是一个结构体的起始地址,从而将 q 的地址拷贝给 p 指针。

所以,上文里提到的 code1 自然是不对了。于是,我改成了第三行代码即 code2。

这种写法深刻秉承了「拷贝区块」的机器操作理念,自然是没错了,可惜还是不够美观。赶着吃饭匆匆 commit 后,才在吃饭期间突然意识到,妹的,这不就是一个指针数组操作嘛!于是,最终形成了 code3 的模样……

多么简单的一个问题。多么深刻的领悟。多么痛的自黑。

2. 再举一个 two star programming 的例子。

来源于一篇博客,也是 Linus Torvalds 在一次访谈里特地提及的「喜欢的 really core low-level kind of coding」。

既然已经把此文写的如此深入浅出了,也就不怕索性再多花点时间,写的更深入浅出一点了…… 囧 上图:

从循环的 invariant(不变量)角度来看,entry 里存储的,一直是下一个「判断是否删除」的链表实体,curr 里存储的,则一直是「被判断是否删除的链表实体的前一个实体」。

这需要两方面保证:一,是结构体第一个成员必须是 next 指针,否则 head / initial entry 会存在不一致性;二,传入的参数是 double star pointer 以保证 head 指针本身可被修改,而不是像博客里第一种方法那样,仅传入一个 pointer 指针(C 语言的函数参数都是传值调用),需要 return head 的返回值,否则也存在不一致性。

根据上面这个图,是不是更容易理解博客代码里,遍历链表的机制呢? :-)

愿天底下所有指针终成眷属。

「2014-3-17」C pointer again …的更多相关文章

  1. 「模拟8.17」star way to heaven(并查集,最小生成树)

    80分打法 首先二分最后答案,答案即为r,可看作以每个k为圆心r为半径的圆 我们进行并查集维护,维护相交的圆的边界 最后判断是否存在圆将上下边界覆盖,如有证明不行 1 #include<iost ...

  2. SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...

  3. 「面向打野编程」iOS多线程:CGD

    「面向打野编程」iOS多线程:CGD 前言 参考网络其他文章而写,渣水平,抛砖引玉. 虽然Concurrent意思为并发,但由于队列的实际效果,以下称为并行队列. 当前iPhone的CPU核心数远小于 ...

  4. 报名|「OneAPM x DaoCloud」技术公开课:Docker性能监控!

    如今,越来越多的公司开始 Docker 了,「三分之二的公司在尝试了 Docker 后最终使用了它」,也就是说 Docker 的转化率达到了 67%,同时转化时长也控制在 60 天内. 既然 Dock ...

  5. 企业运营对 DevOps 的「傲慢与偏见」

    摘要:出于各种原因,并非所有人都信任 DevOps .有些人觉得 DevOps 只不过给开发者改善产品提供了一个途径而已,还有的人觉得 DevOps 是一堆悦耳的空头支票,甚至有人认为 DevOps ...

  6. LOJ6003 - 「网络流 24 题」魔术球

    原题链接 Description 假设有根柱子,现要按下述规则在这根柱子中依次放入编号为的球. 每次只能在某根柱子的最上面放球. 在同一根柱子中,任何2个相邻球的编号之和为完全平方数. 试设计一个算法 ...

  7. Libre 6013 「网络流 24 题」负载平衡 (网络流,最小费用最大流)

    Libre 6013 「网络流 24 题」负载平衡 (网络流,最小费用最大流) Description G 公司有n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等.如何用最少搬运量可以使n ...

  8. LibreOJ #6013. 「网络流 24 题」负载平衡 最小费用最大流 供应平衡问题

    #6013. 「网络流 24 题」负载平衡 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   题目描述 ...

  9. LibreOJ #6001. 「网络流 24 题」太空飞行计划 最大权闭合图

    #6001. 「网络流 24 题」太空飞行计划 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:Special Judge 上传者: 匿名 提交提交记录统计讨论测 ...

随机推荐

  1. Python—函数的参数组合

    参数组合 在Python中定义函数,可以用必选参数.默认参数.可变参数.关键字参数和命名关键字参数,这5种参数都可以组合使用.但是请注意,参数定义的顺序必须是:必选参数.默认参数.可变参数.命名关键字 ...

  2. 新博客地址: kuangbin.org

    RT. 买了新域名,原来的kuangbin.net已经废了,数据已经移动到了kuangbin.org

  3. Xcode LaunchImage 载入界面大小设置

    iPhone Portrait iOS 8-Retina HD 5.5 (1242×2208) @3xiPhone Portrait iOS 8-Retina HD 4.7 (750×1334) @2 ...

  4. CSS3转换

     一.2D转换 1.css3 rotate()旋转 通过指定的角度参数对原元素指定一个2D rotation(2D 旋转) 语法: transform:rotate(<angle>);   ...

  5. 安卓开发 想要获取某个View的高度(我是在做滚动浮层的时候用到的)

    1.activity中有个onWindowFocusChanged()方法,可以获取控件的大小,别的地方可能会调用过早导致获取不到实际的大小 @Override public void onWindo ...

  6. python 循环嵌套

    需求如下: 1. #定义商品列表类 # def shangpin_list(): # for i in com_list: # print(com_list.index(i),end=" & ...

  7. Excel列名 字母和数字的转换

    Excel的列名是由于字母组成的. A-Z 分别代表1-26  AA 是27 AB是28 以此类推. 以下是这种编码的转换方法,如果遇到需要用纯字母编号来表示数字的时候可以用到. /** * 类似EX ...

  8. 论人品 | | noip1015模拟考

    第一题:火车进站... 由于有了老师给的助攻,第一题的时间为半小时,主要在读题了.... jzoj1146 第二题:car 难在正方形的计算? 第二题时间:1.5hour 第三题:sort排序?

  9. C#发送邮件

    以下代码已用 .yeah .qq 以及本人公司邮箱测试通过,可多发,可挂附件 本次测试邮件发送类,是用的winform 页面如下 窗口的后台代码如下: using System; using Syst ...

  10. touchstart,touchmove判断手机中滑屏方向

    滑动屏幕    touchstart:接触屏幕时触发,touchmove:活动过程触发,touchend:离开屏幕时触发 首先获取手接触屏幕时的坐标X,Y //获取接触屏幕时的X和Y$('body') ...