阅读多平台开源c代码小技巧

本文介绍了一种通过Makefile依赖原理,删除与当前平台无关的代码,从而提高阅读效率的方法。以GDB为例,通过该方法删除了70%~80%非关注平台的源代码,并成功编译出可运行的程序。

阅读多平台开源c代码小技巧

linux开源软件特别的较大的程序,一般会进行多平台支持,对使用者来说确实方便了不少,但也增加了阅读难度,增大了学习难度,为了兼容多个平台,代码中少不了一堆的宏及平台相关的特性实现,阅读代码跳转函数时,需小心的甄别平台,甚至需进行调试才能确定执行流程。

本文介绍了一种方法,通过Makefile依赖原理,删除与我们关注的平台无关的代码,同时不影响编译调试,提高阅读代码效率,以GDB为例,通过本方法,删除70%~80%非当前关注平台的源代码,且能编译出可运行的GDB程序。

原理

原理比较简单,源码编译生成执行文件前,只有指定平台相关的文件才会参与编译,非指定平台的代码不会参与编译,编译过程中会读文件,而读文件时则会更新文件的访问时间,编译完成后,如访问时间戳未更新的话,则认为是非关注的文件,不影响编译而可以删除。

时间戳

为实现不关心的代码删除,先了解文件时间戳的概念 。
在windows下,一个文件有:创建时间、修改时间、访问时间。
而在Linux下,一个文件也有三种时间,分别是:访问时间、修改时间、状态改动时间。
1. 访问时间(Access time),读一次这个文件的内容,这个时间就会更新。比如对这个文件运用 more、cat等命令。ls、stat命令都不会修改文件的访问时间。Makefile检查依赖的时候不会更新访问时间戳,但编译的时候会更新访问时间。
2. 修改时间(Modify time),修改时间是文件内容最后一次被修改时间。比如:vi后保存文件。ls -l列出的时间就是这个时间。
3. 状态改动时间(Change time)。是该文件的i节点最后一次被修改的时间,通过chmod、chown命令修改一次文件属性,这个时间就会更新。

注意所谓的linux下,不能是虚拟机windows共享目录,windows共享目录时间戳更新方式与linux下完全不同.

sw@t3swing:gdb-5.2.1$ date +%s
1502951841
sw@t3swing:gdb-5.2.1$ touch COPYING
sw@t3swing:gdb-5.2.1$ stat -c %X COPYING
1502951864

date命令用例显示或者设置系统时间

选项+%s表示以1970年以来的秒数来显示.

touch命令如文件不存在,则创建文件,存在则更新时间戳(3个时间都会更新).
stat命令可以查看文件状态,包括大小,读取时间等信息.

选项-c %X表示查看文件最后读取时间,且以1970年以来的秒数来显示

先做一个简单的验证,看一下下面的测试例子。

sw@t3swing :awk$ touch a.txt
sw@t3swing :awk$ date +%s
1502961586
sw@t3swing:awk$ touch b.txt
sw@t3swing:awk$ stat -c %X *.txt
1502961577
1502961591
sw@t3swing:awk$ ls
a.txt b.txt
sw@t3swing:awk$ find . -type f|awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961586 ]]; then rm “\$1”;fi”;system(cmd); }’
sw@t3swing:awk\$ ls
b.txt
sw@t3swing:awk\$

上面例子流程是:

  1. 先创一个文件a.txt
  2. 获取现在的时间T
  3. 再创一个文件b.txt
  4. 然后查找所有的文件,并删除在时间T前的文件

可以看到成功删除了a.txt,即可以做到删除指定时间的文件。find命令比较强大,应该也可以单独实现上述的效果,但awk比较直接灵活,用单条命令就比较方便的实现需要的功能,注意awk的命令中的cmd,命令与变量$1的组合使用了双引号,与c语言的字符串组合写法类似。

访问时间戳更新问题

有了上面的验证,应该就可以完成开源软件去除非关注文件的工作了,但不幸的是现在的linux 系统,mount的时候一般默认加了relatime选项( linux 2.6.30后的版本,基本把这个作为默认选项),此选项启用时,访问时间的更新很不靠谱(并非每次读文件时都更新读时间戳)。 可以用如下命令看一下mount是否加了relatime选项 .

sw@t3swing:~$ cat /proc/mounts |grep relatime
none /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
none /proc proc rw,nosuid,nodev,noexec,relatime 0 0
none /dev devtmpfs rw,relatime,size=250116k,nr_inodes=62529,mode=755 0 0

当mount时增加relatime(或 noatime)选项是,cat grep等命令并不是每次更新访问时间,实际上连续执行cat命令,第一次有可能会更新,后面的都不会更新,增加这个选项的原因是,更新Access time会降低文件系统效率,特别是频繁读取的情况,更为明显,再说访问时间重要性也没那么大,所以就造成了这种情况,由于这个原因,对我们实现去除无关代码是非常不利的.

这个地方坑了我半天,但方法还是有的,在relatime模式下,我发现,对文件copy操作后,第一次对新的文件执行cat等操作必定会更新其访问时间.(ubuntu系统)

sw@t3swing:~$ cp a.txt new.txt
sw@t3swing:~$ stat -c %x new.txt
2017-09-05 18:23:08.980065942 +0800
sw@t3swing:~$ cat new.txt >/dev/null
sw@t3swing:~$ stat -c %x new.txt
2017-09-05 18:24:12.939930774 +0800

注意
* 需在linux目录下(非windows共享目录)完成该工作。
* 确保新copy的目录中执行删除文件操作,规避访问时间不更新问题.

删除gdb不关注代码步骤

  1. 下载gdb,配置好平台相关参数,并编译通过(make命令),生成gdb可执行文件,并能执行。
  2. make clean,保证工程干净,copy一份做备份,或者直接在备份的源码中操作。可能不会一次执行成功,而且会删除其他平台代码(不关注,但不是说没用),所以备份时必要的。
  3. date +%s记录下当前时间,在此时间之前的c文件都将删除。
  4. 这里在备份代码中验证,进入备份源码目录,make clean,再make,编译出可执行文件,让需编译的文件读取时间戳更新,再make clean。
  5. 执行如下脚本,删除不关注文件,注意替换时间,两条语句选一条使用,第二条语句会打印出已删除的文件。

    find . -name *.c |awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961254 ]]; then rm “$1”;fi”;system(cmd); }’
    find . -name *.c |awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961254 ]]; then rm “$1”;echo \”rm “$1”\”; fi”;system(cmd); }’

  6. 再make,make的时候可能存在编译不通过的情况,遇到依赖的文件,从原工程copy过来即可,直到编译没有错误为止,这种错误不会很多,gdb5.2编译的时候只有一个文件找不到,其他都能顺利编译通过。
    运行可执行程序进行验证,经过验证,编译出的gdb程序可以运行。

注意 date +%s记录下当前时间后,必须确保执行一次make clean与make,否则会删错

可以通过下面命令看一下去除无关文件的结果,gdb-5.2.1是源代码,gdb-bak是经过删除后的代码,

sw@t3swing:gdb-5.2.1$ find . -name *.c |wc
1334 1334 29396
sw@t3swing:gdb-5.2.1$ find . -name *.c |xargs cat |wc
1075774 3995237 31013585

sw@t3swing:gdb-bak$ find . -name *.c |wc
285 285 5346
sw@t3swing:gdb-bak$ find . -name *.c |xargs cat |wc
286718 1060433 8154118

可以看到,GDB5.2的源码共1334个c代码文件,大概107万行,经过删除后,共285个c文件,大概28万行,删除了70%~80%的代码,其中还包含除gdb之外的几个可执行程序,真正需关注的代码可能就几万行,阅读难度没想象的高,实际阅读的时候,使用source insight进行代码调转时,较少看到一个函数在多个文件中实现的情况,为阅读代码节省不少时间。

存在问题

大家可以看到,通过上面的方式删除不关注的代码,只删除了c文件,而头文件则没有删除,更不用说里面的宏了,这就是这种方法的局限性,这种方式解决不了。这里对两个问题进行说明。

  1. 为何只删除c文件,而不删头文件,实际上平台相关的头文件也非常多
    通过这种方式实现不了,看了一下makefile,各平台的头文件都存在依赖,也就是说,编译某个平台,其他平台的头文件Makefile也会检查,删除一个头文件,就会导致编译不过,除非修改Makefile,否则不能删除其他平台的头文件,个人现在的做法是,加入到source insight工程时,手动删除不需关注的平台代码。
  2. 删除平台不相关的c文件后,为何会存在编译不通过的问题
    理论上说,剩余的文件应该都是可执行程序需要的文件,不应该出现缺文件的问题,情况与上一条类似,也是Makefile导致的,Makefile检测时间戳时,不会导致文件的读时间戳更新,而Makefile却依赖该文件,简单说就是Makefile依赖了这个c文件,但这个c文件又不参与编译,所以被误删了,这种情况一般不会太多,个人是手动还原回去的。

这个小技巧对阅读开源代码有一些帮助,但不完善,有兴趣的朋友可以探讨完善。

另外

宏定义确定

宏的用户过于灵活,可以定义成整形,字串,语句或者是函数,可以在代码中定义也可以通过编译参数-D传递,阅读源码时,确定宏是否定义很重要,如何确定一个宏是否定义,方法:

  1. 通常的可以搜索相关的宏,查看相关调用,可以全局搜索排查
    find . -name “*.c” |xargs grep SELECT_ARCHITECTURES
  2. 宏可能通过编译参数传递的,可以改动一下当前文件(如加个空格),或使用touch命令更新当前文件的时间戳,看编译时的编译参数.如-DSELECT_ARCHITECTURES=&bfd_i386_arch
  3. 可以通过错误语法来检测,如下就是一种方式.
    #ifndef SELECT_ARCHITECTURES
    )
    #endif
  4. 上面的方式都是在编译阶段确认,如知道宏的类型,也可以在代码中打印出宏的具体值

符号找不到

符号包含全局量,函数等,有时候定义的方式比较绕,不容易找到,符号如果有使用,且编译通过,那么符号肯定存在,可以先导出一份符号表来确认.
readelf -s ~/gdb > sym.txt

为了兼容多平台情况(面向对象),很多函数都使用指针的形式调用,由于初始化过程可能比较曲折,可以先把函数地址打印出来,然后到符号表中查找,通过地址找到函数名.
查找符号也可以在.o文件中查找,

find . -name “*.o” |awk ‘{cmd = “readelf -s “$1”|grep bfd_elf32_i386_vec;if [[ $? == 0 ]]; then ls -l “$1”;fi;”; system(cmd); }’

sw@t3swing:gdb-bak$ readelf -s gdb/gdb |grep 80caaa0
2789: 080caaa0 45 FUNC GLOBAL DEFAULT 14 find_default_create_infer

#define TARGET_LITTLE_SYM bfd_elf32_i386_vec

<br> ◎ 文件说明<br> <br> 本文件包括以下内容:<br> <br> ※ 1、文件说明<br> ※ 2、源码操作说明<br> ※ 3、光盘目录清单<br> <br><br> ◎ 源码操作说明<br><br> 源代码使用方法是(以实例1为例):<br> 将该实例的源码,比如实例1的1.c文件(可以在001目录下找到),<br> 拷贝到tc编译器目录下,运行tc.exe,打开编译器,<br> 按【F3】键或者“File->Open”菜单命令,打开1.c文件,<br> 按【Ctrl+F9】键,或者“Run->Run”菜单命令,编译运行该程序。<br> <br><br> ◎ 光盘目录清单如下:<br><br>第一部分 基础篇<br> <br>001 第一个C程序 <br>002 运行多个源文件 <br>003 求整数之积 <br>004 比较实数大小 <br>005 字符的输出 <br>006 显示变所占字节数 <br>007 自增/自减运算 <br>008 数列求和 <br>009 乘法口诀表 <br>010 猜数字游戏 <br>011 模拟ATM(自动柜员机)界面 <br>012 用一维数组统计学生成绩 <br>013 用二维数组实现矩阵转置 <br>014 求解二维数组的最大/最小元素 <br>015 利用数组求前n个质数 <br>016 编制万年历 <br>017 对数组元素排序 <br>018 任意进制数的转换 <br>019 判断回文数 <br>020 求数组前n元素之和 <br>021 求解钢材切割的最佳订单 <br>022 通过指针比较整数大小 <br>023 指向数组的指针 <br>024 寻找指定元素的指针 <br>025 寻找相同元素的指针 <br>026 阿拉伯数字转换为罗马数字 <br>027 字符替换 <br>028 从键盘读入实数 <br>029 字符行排版 <br>030 字符排列 <br>031 判断字符串是否回文 <br>032 通讯录的输入输出 <br>033 扑克牌的结构表示<br>034 用“结构”统计学生成绩 <br>035 报数游戏 <br>036 模拟社会关系 <br>037 统计文件的字符数 <br>038 同时显示两个文件的内容 <br>039 简单的文本编辑器 <br>040 文件的字数统计程序 <br>041 学生成绩管理程序 <br> <br>第二部分 数据结构篇<br> <br>042 插入排序 <br>043 希尔排序 <br>044 冒泡排序 <br>045 快速排序 <br>046 选择排序 <br>047 堆排序 <br>048 归并排序 <br>049 基数排序 <br>050 二叉搜索树操作 <br>051 二项式系数递归 <br>052 背包问题 <br>053 顺序表插入和删除 <br>054 链表操作(1) <br>055 链表操作(2) <br>056 单链表就地逆置 <br>057 运动会分数统计 <br>058 双链表 <br>059 约瑟夫环 <br>060 记录个人资料 <br>061 二叉树遍利 <br>062 浮点数转换为字符串 <br>063 汉诺塔问题 <br>064 哈夫曼编码 <br>065 图的深度优先遍利 <br>066 图的广度优先遍利<br>067 求解最优交通路径 <br>068 八皇后问题<br>069 骑士巡游 <br>070 用栈设置密码 <br>071 魔王语言翻译 <br>072 火车车厢重排 <br>073 队列实例 <br>074 K阶斐波那契序列 <br> <br>第三部分 数值计算与趣味数学篇<br> <br>075 绘制余弦曲线和直线的迭加<br>076 计算高次方数的尾数 <br>077 打鱼还是晒网 <br>078 怎样存钱以获取最大利息 <br>079 阿姆斯特朗数 <br>080 亲密数 <br>081 自守数 <br>082 具有abcd=(ab+cd)2性质的数 <br>083 验证歌德巴赫猜想<br>084 素数幻方 <br>085 百钱百鸡问题 <br>086 爱因斯坦的数学题 <br>087 三色球问题<br>088 马克思手稿中的数学题 <br>089 配对新郎和新娘 <br>090 约瑟夫问题<br>091 邮票组合 <br>092 分糖果 <br>093 波瓦松的分酒趣题 <br>094 求π的近似值 <br>095 奇数平方的有趣性质<br>096 角谷猜想 <br>097 四方定理 <br>098 卡布列克常数 <br>099 尼科彻斯定理 <br>100 扑克牌自动发牌 <br>101 常胜将军 <br>102 搬山游戏<br>103 兔子产子(菲波那契数列) <br>104 数字移动 <br>105 多项式乘法 <br>106 产生随机数 <br>107 堆栈四则运算 <br>108 递归整数四则运算 <br>109 复平面作图 <br>110 绘制彩色抛物线 <br>111 绘制正态分布曲线 <br>112 求解非线性方程 <br>113 实矩阵乘法运算<br>114 求解线性方程 <br>115 n阶方阵求逆 <br>116 复矩阵乘法 <br>117 求定积分 <br>118 求满足特异条件的数列 <br>119 超长正整数的加法 <br> <br>第四部分 图形篇<br> <br>120 绘制直线 <br>121 绘制圆 <br>122 绘制圆弧 <br>123 绘制椭圆<br>124 设置背景色和前景色<br>125 设置线条类型 <br>126 设置填充类型和填充颜色 <br>127 图形文本的输出 <br>128 金刚石图案<br>129 飘带图案 <br>130 圆环图案 <br>131 肾形图案 <br>132 心脏形图案 <br>133 渔网图案 <br>134 沙丘图案<br>135 设置图形方式下的文本类型 <br>136 绘制正多边形 <br>137 正六边形螺旋图案 <br>138 正方形螺旋拼块图案<br>139 图形法绘制圆 <br>140 递归法绘制三角形图案 <br>141 图形法绘制椭圆 <br>142 抛物样条曲线 <br>143 Mandelbrot分形图案<br>144 绘制布朗运动曲线 <br>145 艺术清屏 <br>146 矩形区域的颜色填充 <br>147 VGA256色模式编程 <br>148 绘制蓝天图案 <br>149 屏幕检测程序 <br>150 运动的小车动画 <br>151 动态显示位图 <br>152 利用图形页实现动画<br>153 图形时钟 <br>154 音乐动画 <br> <br>第五部分 系统篇<br> <br>155 读取DOS系统中的国家信息 <br>156 修改环境变 <br>157 显示系统文件表 <br>158 显示目录内容 <br>159 读取磁盘文件 <br>160 删除目录树 <br>161 定义文本模式 <br>162 设计立体窗口 <br>163 彩色弹出菜单 <br>164 读取CMOS信息 <br>165 获取BIOS设备列表 <br>166 锁住硬盘 <br>167 备份/恢复硬盘分区表 <br>168 设计口令程序 <br>169 程序自我保护<br> <br>第六部分 常见试题解答篇<br> <br>170 水果拼盘 <br>171 小孩吃梨 <br>172 删除字符串中的特定字符 <br>173 求解符号方程 <br>174 计算标准差 <br>175 求取符合特定要求的素数 <br>176 统计符合特定条件的数 <br>177 字符串倒置 <br>178 部分排序 <br>179 产品销售记录处理 <br>180 特定要求的字符编码 <br>181 求解三角方程 <br>182 新完全平方数 <br>183 三重回文数 <br>184 奇数方差 <br>185 统计选票 <br>186 同时整除 <br>187 字符左右排序 <br>188 符号算式求解 <br>189 数字移位 <br>190 统计最高成绩 <br>191 比较字符串长度 <br>192 合并整数 <br>193 矩阵逆置<br>194 删除指定的字符<br>195 括号匹配 <br>196 字符串逆置 <br>197 SIX/NINE问题 <br>198 单词个数统计 <br>199 方差运算 <br>200 级数运算 <br>201 输出素数 <br>202 素数题 <br>203 序列排序 <br>204 整数各位数字排序 <br>205 字符串字母移位 <br>206 Fibonacc数列 <br> <br>第七部分 游戏篇<br> <br>207 商人过河游戏 <br>208 吃数游戏 <br>209 解救人质游戏 <br>210 打字训练游戏 <br>211 双人竞走游戏 <br>212 迷宫探险游戏 <br>213 迷你撞球游戏<br>214 模拟扫雷游戏 <br>215 推箱子游戏 <br>216 五子棋游戏 <br> <br>第八部分 综合实例篇<br> <br>217 综合CAD系统 <br>218 功能强大的文本编辑器<br>219 图书管理系统<br>220 进销存管理系统
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值