「2014-3-17」C pointer again …
记录一个比较基础的东东…… 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 …的更多相关文章
- 「模拟8.17」star way to heaven(并查集,最小生成树)
80分打法 首先二分最后答案,答案即为r,可看作以每个k为圆心r为半径的圆 我们进行并查集维护,维护相交的圆的边界 最后判断是否存在圆将上下边界覆盖,如有证明不行 1 #include<iost ...
- SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」
有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...
- 「面向打野编程」iOS多线程:CGD
「面向打野编程」iOS多线程:CGD 前言 参考网络其他文章而写,渣水平,抛砖引玉. 虽然Concurrent意思为并发,但由于队列的实际效果,以下称为并行队列. 当前iPhone的CPU核心数远小于 ...
- 报名|「OneAPM x DaoCloud」技术公开课:Docker性能监控!
如今,越来越多的公司开始 Docker 了,「三分之二的公司在尝试了 Docker 后最终使用了它」,也就是说 Docker 的转化率达到了 67%,同时转化时长也控制在 60 天内. 既然 Dock ...
- 企业运营对 DevOps 的「傲慢与偏见」
摘要:出于各种原因,并非所有人都信任 DevOps .有些人觉得 DevOps 只不过给开发者改善产品提供了一个途径而已,还有的人觉得 DevOps 是一堆悦耳的空头支票,甚至有人认为 DevOps ...
- LOJ6003 - 「网络流 24 题」魔术球
原题链接 Description 假设有根柱子,现要按下述规则在这根柱子中依次放入编号为的球. 每次只能在某根柱子的最上面放球. 在同一根柱子中,任何2个相邻球的编号之和为完全平方数. 试设计一个算法 ...
- Libre 6013 「网络流 24 题」负载平衡 (网络流,最小费用最大流)
Libre 6013 「网络流 24 题」负载平衡 (网络流,最小费用最大流) Description G 公司有n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等.如何用最少搬运量可以使n ...
- LibreOJ #6013. 「网络流 24 题」负载平衡 最小费用最大流 供应平衡问题
#6013. 「网络流 24 题」负载平衡 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 ...
- LibreOJ #6001. 「网络流 24 题」太空飞行计划 最大权闭合图
#6001. 「网络流 24 题」太空飞行计划 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:Special Judge 上传者: 匿名 提交提交记录统计讨论测 ...
随机推荐
- Java for循环的几种用法
J2SE 1.5提供了另一种形式的for循环.借助这种形式的for循环,可以用更简单地方式来遍历数组和Collection等类型的对象.本文介绍使用这种循环的具体方式,说明如何自行定义能被这样遍历的类 ...
- 【面向对象版】HashMap(增删改查)
前言: 关于什么是HashMap,HashMap可以用来做些什么,这些定义类的描述,请参照[简易版]HashMap(增删改查)的内容. 这章节主要是面向实例,直接进行HashMap(增删改查)的演示. ...
- NSMutableAttributedString的使用
1.设置字符串中数字字符串的颜色: // 设置字符串中数字的颜色 - (void)setTextColor:(UILabel *)label FontNumber:(id)font AndRange: ...
- Appium移动自动化测试之问题总结
1.运行该测试用例,报如下错误 java.lang.NoSuchMethodError: org.openqa.selenium.remote.ErrorHandler.<init>(Lo ...
- iOS 自定义图片和文字垂直显示按钮<上面是图片,文字显示下面>
#import <UIKit/UIKit.h> @interface VerticalButton : UIButton @end #import "VerticalButton ...
- submit 后台运行代码
if EX_TABLE-matnr is not initial and EX_TABLE-KUNDE is not initial. wa_rspar-selname = 'MATNR' ...
- OC中类别、扩展、协议与委托
一.类别(category) 类别(category)——通过使用类别,我们可以动态地为现有的类添加新方法,而且可以将类定义模块化地分不到多个相关文件中.通常只在类别中定义方法.(类别,接口部分的定义 ...
- [转] Oracle数据库备份与恢复 - 增量备份
转:http://blog.csdn.net/pan_tian/article/details/46780929 RMAN一个强大的功能是支持增量备份,增量备份中心思想就是减少备份的数据量,我们不 ...
- LSD-SLAM深入学习(2)-算法解析
前言 在LSD-SLAM深入学习(1)中我们已经完成基本的安装与测试,在此我们继续解析算法与代码,由于lsd-slam本身利用了一部分李群与李代数的知识,需要一定的数学功底. 个人理解错误的地方还请不 ...
- C#实现自动单击
最新玩了一下上学时候玩的游戏,但游戏里面变化太多了,进去后等级就很高,要不停地点击鼠标加技能. 所以利用工作中常用的C#调用 API不停地点击鼠标. 如图: 为方便在基础上修改,我把整个解决方案放到百 ...