记录一个比较基础的东东…… 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 的返回值,否则也存在不一致性。

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

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

C pointer again …的更多相关文章

  1. 苹果手机不支持click文字 需要添加 cursor:pointer 才能 识别可以点击

    给一个div 绑定一个 click事件,  苹果手机会识别不了,必须添加一个 cursor:pointer 才能 识别可以点击.安卓正常识别.

  2. [LeetCode] Copy List with Random Pointer 拷贝带有随机指针的链表

    A linked list is given such that each node contains an additional random pointer which could point t ...

  3. Pointer's NULL And 0

    问题起源 在使用Qt框架的时候, 经常发现一些构造函数 *parent = 0 这样的代码. 时间长了, 就觉的疑惑了. 一个指针不是等于NULL吗? 这样写, 行得通吗? 自己测试一下就可以了. 测 ...

  4. C++中Reference与Pointer的不同

    Reference与Pointer中直接存储的都是变量的地址, 它们唯一的不同是前者的存储的地址值是只读的, 而后者可以修改. 也就是说Reference不支持以下操作: *a = b 其他语言, 如 ...

  5. LeetCode——Copy List with Random Pointer(带random引用的单链表深拷贝)

    问题: A linked list is given such that each node contains an additional random pointer which could poi ...

  6. Leetcode Copy List with Random Pointer

    A linked list is given such that each node contains an additional random pointer which could point t ...

  7. 移动端/H5关于cursor:pointer导致的问题

    cursor属性规定要显示的光标的类型(形状),该属性定义了鼠标指针放在一个元素边界范围内时所用的光标形状(不过 CSS2.1 没有定义由哪个边界确定这个范围). 不过,这个属性用在PC端没有任何问题 ...

  8. 关于编译报错“dereferencing pointer to incomplete type...

    今天同事问了我一个问题,他make的时候报错,“第201行:dereferencing pointer to incomplete type”,我随即查阅了很多资料,也没看出个所以然.最后问题得到了解 ...

  9. pointer to function

    指针.函数.数字.结构体.指针函数.函数指针 初学不好区分,做点儿实验来有效区分一下,以下代码采用dev-C++平台测试 //pointer to fucntion 函数功能是 基地址加偏移量得到偏移 ...

  10. TObject、Pointer、Interface的转换

    unit Unit4; ));   ));   ));   //将Obj转为接口   //LInf1 := ITest(Pointer(LObj1));       //无法转换了,丢失了接口信息   ...

随机推荐

  1. thinkphp 5 _initialize 使用问题

    如果继承的是common的话.控制器的_initialize要先继承父类的_initialize parent::_initialize();

  2. 如何使用wepy和 vant-weapp开发小程序

    这里记录一下  使用wepy框架和  vant-weapp库开发小程序废话 不多说 wepy文档: https://tencent.github.io/wepy/document.html#/ van ...

  3. SpringCloud-day04-Eureka高可用集群配置

    5.4Eureka高可用集群配置 在高并发的情况下一个注册中心难以满足,因此一般需要集群配置多台. 我们再新建两个module  microservice-eureka-server-2002,  m ...

  4. python 练习题(1-15)

    1.给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 2.生成双色球 3.逻辑运算(运算符优先级) 4.输入一个整数,判断这个数是几位数 5.用while循环计算 1-2+3-4...-9 ...

  5. 解题(Solution -4Sum)

    Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = tar ...

  6. Swift UITableView嵌套UICollectionView点击事件冲突(点击事件穿透)

    不管是啥都响应tableviewcell class JYShopCertificationCell: UITableViewCell { override func hitTest(_ point: ...

  7. shp2pgsql向postgresql导入shape数据

    1. 准备好Shape文件(不仅仅是.shp文悠扬,还要有其他相关数据文件,包括.shx..prj..dbf文件). 2. 使用命令将Shape数据转换为*.sql文件 shp2pgsql -s 38 ...

  8. java 将指定文件夹递归的进行zip打包压缩

    package tmp.MavenTest; import java.io.BufferedInputStream; import java.io.File; import java.io.FileI ...

  9. java程序员经常使用的Intellij Idea插件

    大概从去年年初开始慢慢抛弃习惯多年的eclipse,开始使用Intellij Idea,以下是我使用过的一些Intellij Idea插件: 1.lombok https://plugins.jetb ...

  10. nginx学习笔记(二)

    nginx变量 Nginx 变量值容器的生命期是与当前正在处理的请求绑定的,而与 location 无关. 通过 set 指令隐式创建的 Nginx 变量.这些变量我们一般称为"用户自定义变 ...