「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 上传者: 匿名 提交提交记录统计讨论测 ...
随机推荐
- chattr无法删除某个文件
chattr LNMP无法删除或更改权限,显示:rm: cannot remove `.user.ini': Operation not permitted 无法删除".user.ini&q ...
- Ruby-模块和类
首先看下他们的关系 irb(main):100:0> String.class => Class irb(main):101:0> String.class.superclass ...
- ---JS canvas学习笔记
context的fillStyle属性 fillStyle=color | gradient | image | canvas |video strokeStyle也有上述属性. 1.color:#f ...
- Java对象的序列化和反序列化
对象的序列化是指将对象转换为字节序列的过程 对象的反序列化是指将字节序列恢复对象的过程 主要有两种用途: 1.把对象的字节序列永久地保存在硬盘上,通常放在一个文件中. 2.在网络上传输对象的字节序列. ...
- 利用bootstrap写图片轮播
利用bootstrap写图片轮播 缺点是轮播没有固定样式图片样式会改变外框的大小,所以要再设置 以及左右按钮的style也要从新设置 <div class="carousel slid ...
- grep使用多个查询条件--或
[root@mail ~]# grep 'usrquota\|grpquota' /etc/fstabLABEL=/1 / e ...
- ios UILabel在storyBoard或xib中如何在每行文字不显示完就换行
大家知道怎么用代码让label中的文字换行,只需要 label.numberOfLines = 0; label.text = @"这是第一行啦啦啦啦,\n这是第二行啦啦,\n这是第三行&q ...
- 转:C# WinForm获取 当前执行程序路径的几种方法
1.获取和设置当前目录的完全限定路径. string str = System.Environment.CurrentDirectory; Result: C:xxxxxx 2.获取启动了应用程序的可 ...
- C++读写文件ofstream,ifstream,fstream)[转]
在看C++编程思想中,每个练习基本都是使用ofstream,ifstream,fstream,以前粗略知道其用法和含义,在看了几位大牛的博文后,进行整理和总结: 这里主要是讨论fstream的内容:[ ...
- 接入百度语音SDK的步骤
1.导入依赖库 SystemConfiguration.framework AudioToolbox.framework UIkit.framework AVFoundation.framework ...