记录一个比较基础的东东…… 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. Navicat Premium 12注册机使用教程

    来看下软件具体的安装.激活图文教程: 1.首先下载后正常安装软件至结束: 这时候如果打开的话,是提示要注册的 2.以管理员身份运行注册机工具 Navicat_Keygen_Patch[vxia.net ...

  2. windows操作系统python selenium webdriver安装

    这几天想搞一个爬虫,就来学习一下selenium,在网上遇见各种坑,特写一篇博文分享一下selenium webdriver的安装过程. 一.安装selenium包 pip install selen ...

  3. vue 自定义组件销毁

    今天在开发电商vue前端项目时,用户每次登出再换其它用户登录时,页面显示的用户名和左则导航都还是上个用户的,刚开始以为是localStorage中没有清除全局数据,然后在用户点击退出系统时手动清除lo ...

  4. 配置错误 不能在此路径中使用此配置节。如果在父级别上锁定了该节,便会出现这种情况。锁定是默认设置的(overrideModeDefault="Deny"),或者是通过包含 overrideMode="Deny" 或旧有的 allowOverride="false" 的位置标记明确设置的。

    原因:可能是在安装IIS7的时候没有安装asp.net, 尝试使用以下方法: cmd.exe要以管理员身份启动,在c:\windows\system32下找到cmd.exe,右键管理员启动,输入命令 ...

  5. Linux下使用ps命令查看某个进程文件的启动位置

    ps -ef|grep shutdown ls -al /proc/4170

  6. json和jquery中的ajax

    JSON: java script Object otation:js对象标记 声明一个json对象,使用key:value对应,中间用冒号连接,键值对之间用逗号连接,最外面用{}包含 声明方式: 语 ...

  7. php+javascript实现的动态显示服务器运行程序进度条功能示例

    本文实例讲述了php+javascript实现的动态显示服务器运行程序进度条功能.分享给大家供大家参考,具体如下: 经常有这样的业务要处理,服务器上有较多的业务需要处理,需要分批操作,于是就需要一个提 ...

  8. mysql-----04 多表查询

    本节主要介绍mysql的多表查询(多表连接查询.复合条件查询.子查询) 一.多表连接查询 #重点:外链接语法 select 字段列表 from 表1 inner|left|right join 表2 ...

  9. ThinkPHP3.2 --- 中文乱码问题

    在thinkphp中初次运行时 会出现中文乱码问题,解决方法也很简单 只需要在入口文件index.php加上这段代码即可: <?php header("Content-Type: te ...

  10. 机器学习(四)--------逻辑回归(Logistic Regression)

    逻辑回归(Logistic Regression) 线性回归用来预测,逻辑回归用来分类. 线性回归是拟合函数,逻辑回归是预测函数 逻辑回归就是分类. 分类问题用线性方程是不行的   线性方程拟合的是连 ...