clang_intprt_t类型探究
作者:玄魂工作室-钱海龙
问题
这篇手把手教你构建 C 语言编译器,里面有着这样的代码
void eval() {
int op, *tmp;
while (1) {
if (op == IMM) {ax = *pc++;} // load immediate value to ax
else if (op == LC) {ax = *(char *)ax;} // load character to ax, address in ax
else if (op == LI) {ax = *(int *)ax;} // load integer to ax, address in ax
else if (op == SC) {ax = *(char *)*sp++ = ax;} // save character to address, value in ax, address on stack
else if (op == SI) {*(int *)*sp++ = ax;} // save integer to address, value in ax, address on stack
}
...
return 0;
}
只看op == LC这段代码,ax是一个int类型,存放的值是char *指针类型地址,取完该地址所在的值再赋给变量ax
但是如此写代码,vim的youcomplete插件一直报错

那就举个例子
//test.c
#include <stdio.h>
int main() {
int a = 1;
int p = &a;
printf("the result is %d\n",*((int*)p));
}
32位linux gcc v4.8.4
试试32位gcc
~$ gcc test.c -o test
test.c: In function ‘main’:
test.c:4:13: warning: initialization makes integer from pointer without a cast [enabled by default]
int p = &a;
~$ ./test
the result is 1
虽然有警告,依然能运行成功正确输出,接下来试试32位g++
~$ g++ test.c -o test
ltest.c: In function ‘int main()’:
test.c:4:14: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
int p = &a;
直接抛出错误
64位linux gcc version 5.4.0
试试64位gcc
ch@ch-pc:~$ gcc test.c -o test
test.c: In function ‘main’:
test.c:4:13: warning: initialization makes integer from pointer without a cast [-Wint-conversion]
int p = &a;
^
test.c:5:35: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
printf("the result is %d\n",*((int*)p));
^
ch@ch-pc:~$ ./test
段错误 (核心已转储)
运行时才出错,那么试试64位g++
ch@ch-pc:~$ g++ test.c -o test
test.c: In function ‘int main()’:
test.c:4:14: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
int p = &a;
^
test.c:5:41: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
printf("the result is %d\n",*((int*)p));
编译不通过
当然-m32这种参数,就不讨论了
初步结论
g++编译的时候就认为是个错误,gcc32位编译可以正常运行,64位运行时报错
我们探讨一下原因,32位和64的int类型都是4个字节的,但是指针类型的大小不一致
#include <stdio.h>
int main() {
int *p;
printf("the result is %lu\n", sizeof(p));
}
分别在32位和64位编译器(注意是编译器,64位系统也有可能有32位编译器)编译后,运行
32位结果为"the result is 4"
64位结果为"the result is 8"
本质原因
64位,gcc编译后,拿到test可执行程序,程序执行会出现段错误,现在来反汇编一下
//test.c
#include <stdio.h>
int main() {
int a = 1;
int p = &a;
printf("the result is %d\n",*((int*)p));
}
//编译
~$ gcc test.c -o test
// objdump反汇编命令
// -S 尽可能尽可能反汇编出源代码
// -M 因为个人习惯问题,不太会看AT&A的汇编,还是搞成intel的来看
// nl只是把上面的结果显示一下行数,-b 行的显示方式
// -ba //显示所有行号(包括空行)
// -bt //显示所有行号(但不包括空行)
~$ objdump -S -M intel test | nl -ba
主要看一下,main函数

- 从138行开始看,对应着代码int a = 1,将数字1赋值给rbp栈上的-0x10处,也就是在距离bp栈的16字节处(因为0x10=16);如下图1行,B(地址)处的为数字1,占四个字节,那么中间竖线就是[rbp-0xc]处
- 139行,将地址传给了rax寄存器,注意rax是16字节(对应题目中的指针大小),对应下图2行,rax存储的就是(A,B)
- 140行,对应下图3行指令中eax是rax的低位,存储的值就是B(注意B是地址)四个字节,赋值给[rbp-0xc]处四个字节B(注意B是地址),而[rbp-0xc]到[rbp-0x10]还是数字1四个字节
- 最主要的问题出在141行,也就是把[rbp-0xc]的值,也就是B,赋值给rax的低位,本来这个rax的低位8个字节就是B,这个没问题,问题出在64位系统的给eax(rax的低位)赋值,会影响rax的高位,高位全被置为0了. 具体论证在这里
- 这样在143行,对应下图5行,尝试把rax寄存器的值当成地址,去该地址取值. rax寄存器的值是(0, B)(这里面是0, B各占8个字节,对应c代码里面的指针大小,16个字节),而实际需要的地址值是(A, B).

我们来用edb调试的结果,也跟想得一样

有一点我上面并没有讲到,就是上图4行的 rax 过渡到上图5行的时候高位并不一定是零,因为在142行的时候,有一个指令cdqe,这是eax拓展成rax的指令,所有要根据eax的正负性来判断.也就是说,如果eax表达出来是负数,rax的高位补出来的是全f;同理eax正数的情况下,rax高位补全的才是0
解决方案
在c99的标准库里面有一个结构体,intptr_t可以实现编译器位数兼容性
//头文件stdint.h
/* Types for `void *' pointers. */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned long int uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned int uintptr_t;
#endif
上述测试代码改成这样即可
#include <stdio.h>
#include <stdint.h>
int main() {
int a = 1;
int p = &a;
printf("the result is %d\n",*((int*)(intptr_t)p));
}
原始代码改成下面即可
if(op == IMM) {
ax = *pc++;
} else if(op == LC) {
ax = *(char *)(intptr_t)ax;
} else if(op == LI) {
ax = *(int *)(intptr_t)ax;
} else if(op ==SC) {
ax = *(char *)(intptr_t)*sp++ = ax;
} else if(op == SI){
*(int *)(intptr_t)*sp++ = ax;
} else if(op == PUSH) {
*--sp = ax;
} else if(op == JMP) {
pc = (int *)(intptr_t)*pc;
} else if(op == JZ) {
pc = ax ? pc + 1 : (int *)(intptr_t)*pc;
} else if(op == JNZ) {
pc = ax ? (int *)(intptr_t)*pc : pc + 1;
} else if(op == CALL) {
*--sp = (int)(intptr_t)(pc + 1);
pc = (int *)(intptr_t)*pc;
} else if(op == ENT) {
*--sp = (int)(intptr_t)bp;
bp = sp;
sp = sp - *pc++;
} else if(op == ADJ) {
sp = sp + (intptr_t)*pc++;
}
参考
- C---int和指针转换注意事项
- Disabling “cast from pointer to smaller type uint32_t” error in Clang
- Why is “cast from ‘X*’ to ‘Y’ loses precision” a hard error and what is suitable fix for legacy code
- C语言指针转换为intptr_t类型
- x86_64 registers rax/eax/ax/al overwriting full register contents [duplicate]
- CBW/CWDE/CDQE
clang_intprt_t类型探究的更多相关文章
- mysql、sqlserver数据库常见数据类型对应java中的的类型探究
由于本次测试表的结构不涉及到主键的自增长,所以mysql.sqlserver建表语句相同: CREATE TABLE testType ( id INT NOT NULL DEFAULT 0, gen ...
- java.lang.NumberFormatException: Infinite or NaN原因之浮点类型除数为0结果探究
背景 在对Double类型的数据进行计算操作,将结果转化为BigDecimal时抛出了下面的异常,进行了Debug才发现了问题原因,同时也暴露出了自己在一些基础知识上还有些欠缺. Exception ...
- Java 8新特性探究(三)泛型的目标类型推断
简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...
- Java 8新特性探究(二)类型注解和重复注解
本文将介绍java 8的第二个特性:类型注解. 注解大家都知道,从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置.那充满争议的类型注解究竟是什 ...
- 一次修改mysql字段类型引发的技术探究
说来,mysql数据库是我们项目中用的比较多的库,ORM工具喜欢采用细粒度的MyBatis.这里面就这么引出了两者之间的故事! 首先,说改字段吧,将一个表中的varchar字段改为enum字段.如下: ...
- 探究 Redis 4 的 stream 类型
redis 2 10 月初,Redis 搞了个大新闻.别紧张,是个好消息:Redis 引入了名为 stream 的新数据类型和对应的命令,大概会在年底正式发布到 4.x 版本中.像引入新数据类型这样的 ...
- Swift - 值类型与引用类型的初步探究
前言 swift中的结构体和类在组成和功能上具有一定的相似性.两者都可以含有成员属性.成员方法用于数据存储和功能性模块封装.往往造成不知如何对二者进行区分和使用 值类型概念和引用类型概念 值类型的概念 ...
- 记一次批量更新整型类型的列 → 探究 UPDATE 的使用细节
开心一刻 今天,她给我打来电话 她:你明天陪我去趟医院吧 我:怎么了 她:我怀孕了,陪我去打胎 我:他的吗 她:嗯 我心一沉,犹豫了片刻:生下来吧,我养! 她:他的孩子,你不配养! 我:我随孩子姓 需 ...
- MyBatis探究-----返回Map类型数据
1.使用@MapKey @MapKey:告诉mybatis封装Map的时候使用哪个属性作为Map的key Map<K, V>:键是这条记录的主键key,值是记录封装后的javaBean 1 ...
随机推荐
- 前端开发中SEO的十二条总结
一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...
- ASP.NET Aries 入门开发教程5:自定义列表页工具栏区
前言: 抓紧时间,继续写教程,因为发现用户期待的内容,都在业务处理那一块. 不得不继续勤劳了. 这节主要介绍工具栏区的玩法. 工具栏的默认介绍: 工具栏默认包括5个按钮,根据不同的权限决定显示: 添加 ...
- Partition1:新建分区表
未分区的表,只能存储在一个FileGroup中:对Table进行分区后,每一个分区都存储在一个FileGroup,或分布式存储在不同的FileGroup中.对表进行分区的过程,是将逻辑上完整的一个表, ...
- C#基础篇 - 理解委托和事件
1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针.简单理解,委托是一种可以把函数当做参数传递的类型.很多情况下,某 ...
- Android注解使用之注解编译android-apt如何切换到annotationProcessor
前言: 自从EventBus 3.x发布之后其通过注解预编译的方式解决了之前通过反射机制所引起的性能效率问题,其中注解预编译所采用的的就是android-apt的方式,不过最近Apt工具的作者宣布了不 ...
- 当web.config文件放置在共享目录下(UNC),启动IIS会提示有错误信息500.19,伴随有错误代码0x80070003和错误代码0x80070005的解决办法
最近遇到一个很有意思的使用环境,操作人员将所有的网站应用内容投放到共享存储里面,并且使用微软的SMB协议将其以CIFS的方式共享出来,使用Windows Server 2008 R2的IIS将其连接起 ...
- [开发笔记]yum错误
yum 错误TypeError: rpmdb open failed 解决办法 是因为RPM数据库出现损坏导致的,它导致所有的软件的升级.安装甚至是删除都会出现问题,终端出现乱码,YUMEX也用不成, ...
- mongodb
修改所有的记录: > db.t_express_apply.update({},{$set:{"isStatus" : 0}},{multi:true})WriteResul ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- BPM生产安全管理解决方案分享
一.方案概述生产安全管理是企业生产管理的重要组成部分,组织实施好企业安全管理规划.指导.检查和决策,保证生产处于最佳安全状态是安全管理的重要内容和职责.H3 BPM企业生产安全管理解决方案是一套专门为 ...