Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >clang_intprt_t类型探究

clang_intprt_t类型探究

作者头像
用户1631416
发布于 2018-04-12 05:22:58
发布于 2018-04-12 05:22:58
1.1K00
代码可运行
举报
文章被收录于专栏:玄魂工作室玄魂工作室
运行总次数:0
代码可运行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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插件一直报错

那就举个例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~$ 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++

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~$ 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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++

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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个字节的,但是指针类型的大小不一致

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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可执行程序,程序执行会出现段错误,现在来反汇编一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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函数

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//头文件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

上述测试代码改成这样即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <stdint.h>
int main() {
    int a = 1;
    int p = &a;
    printf("the result is %d\n",*((int*)(intptr_t)p));
}

原始代码改成下面即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        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
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 玄魂工作室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++:从技术实现角度聊聊RTTI
第一次接触RTTI,是在<<深度探索c++对象模型>>这本书中,当时对这块的理解比较浅,可能因为知识积累不足吧。后面在工作中用到的越来越多,也逐渐加深了对其认识,但一直没有一个系统的认知,所以抽出一段时间,把这块内容整理下。
高性能架构探索
2023/06/13
1.3K0
C++:从技术实现角度聊聊RTTI
Python内核源码解析与C/CPP-API拓展编程(一)PyObject
基于C++的调试对于已经到Python虚拟机中存储起来的字节码命令是无法被观察到的,我们只能把它们解析成AST才能看懂字节码在解释器内存中的状态,所以这里我们借用Python解释器里的C_API来输出我们的对象:
Pulsar-V
2019/04/18
2.3K0
Python内核源码解析与C/CPP-API拓展编程(一)PyObject
程序机械级表示——数据格式与访问信息
8位称为字节(byte),16位称为字(word),32位为双字(double words),64位为四字(quad words)
Andromeda
2023/10/21
2160
移位溢出
实际项目中需要计算SD卡中某个目录的大小,并判断该目录所占空间是否超过SD卡总容量的一半。 测试过程中经常发现误报,该目录所占空间远小于SD卡容量一半的时候,就上报占用空间过半的事件。 排查发现原来是计算的时候移位导致了溢出。问题代码如下:
coderhuo
2018/08/29
1.2K0
程序的机器级表示
预处理阶段:预处理器cpp根据编译文件以“#”开头的命令,读取系统头文件stdio.h(.h结尾的表示头文件,.c表示可执行文件)的内容,并把它插入到程序文本中,得到一个新的文件。
_春华秋实
2019/02/22
6600
程序的机器级表示
OpenHarmony 内核源码分析(编译过程篇) | 简单案例窥视编译全过程
编译过程要经过:源文件 --> 预处理 --> 编译(cc1) --> 汇编器(as) --> 链接器(ld) --> 可执行文件(PE/ELF)
小帅聊鸿蒙
2025/03/25
920
OpenHarmony 内核源码分析(编译过程篇) | 简单案例窥视编译全过程
万万没想到,一个可执行文件原来包含了这么多信息!
拿到一个编译好的可执行文件,你能获取到哪些信息?文件大小,修改时间?文件类型?除此之外呢?实际上它包含了很多信息,这些你都知道吗?
编程珠玑
2020/06/15
7160
x64架构下Linux系统函数调用
push指令将数据压栈。具体就是将esp(stack pointer)寄存器减去压栈数据的大小,再将数据存储到esp寄存器所指向的地址。
Orlion
2024/09/02
1870
x64架构下Linux系统函数调用
记64位地址截断引发的挂死问题
最近要将整个项目的代码从原先的只支持32位变成同时支持32位和64位,这个过程中遇到一个很不容易定位的挂死问题,花了不少时间才定位解决,因此分享给大家。
编程珠玑
2019/07/12
9190
给32位系统装8g内存条能用吗?为什么?
我们当然很清楚,装软件的时候,一般64位的系统就选64位的软件,肯定不出错,但是这又是为什么呢?既然CPU,软件,操作系统,数值大小都有32位和64位,他们之间就可以随意组合成各种问题,比如32位的系统能装64位的软件吗?32位的系统能计算int64的数值吗?他们之间到底有什么关系?这篇文章会尝试解释清楚。
小白debug
2022/06/20
3K0
给32位系统装8g内存条能用吗?为什么?
程序员C语言快速上手——基础篇(三)
上一节已经讲过,由于C语言中,整型的实际长度和范围不固定的问题,会导致C语言存跨平台移植的兼容问题,因此,C99标准中引入了stdint.h头文件,有效的解决了该问题。
arcticfox
2019/06/26
1.2K0
程序员C语言快速上手——基础篇(三)
触发OSR 编译(以goto指令为例)及安装与卸载
决定一个方法是否为热点代码的因素有两个:方法的调用次数、循环回边的执行次数。即时编译便是根据这两个计数器的和来触发的。为什么 Java 虚拟机需要维护两个不同的计数器呢?
全栈程序员站长
2021/04/07
1.1K0
golang源码分析:go 汇编
AT&T格式的汇编代码中所有寄存器名字前面都有一个%符号,rsp代码sp寄存器,里面存的是栈顶指针。
golangLeetcode
2022/08/02
9410
golang源码分析:go 汇编
x86架构与x64架构在函数于栈中调用过程的不同之处
1、x86架构 x86架构是intel开发的一种32位的指令集。8个32位通用寄存器 eax,ebx,ecx,edx,ebp,esp,esi,edi。
Elapse
2020/08/17
1.9K0
浅谈函数调用!
导语 |  在任意一门编程语言中,函数调用基本上都是非常常见的操作;我们都知道,函数是由调用栈实现的,不同的函数调用会切换上下文;但是,你是否好奇,对于一个函数调用而言,其底层到底是如何实现的呢?本文讲解了函数调用的底层逻辑实现。 一、汇编概述 既然要讲解函数调用的底层逻辑实现,那么汇编语言我们是绕不过的。 因此,首先来复习一下汇编相关的知识。 我们都知道,计算机只能读懂二进制指令,而汇编就是一组特定的字符,汇编的每一条语句都直接对应CPU的二进制指令,比如:mov rax,rdx就是我们常见的汇编指令。
腾讯云开发者
2022/08/26
1.8K0
浅谈函数调用!
C/C++:堆栈面面观
在数据结构中,我们也听过栈和堆这两种数据结构,当然和我本文要讲的东西是不同的概念。不过数据结构中的栈(算法、数学意义上的一种抽象),和本文中的栈(实际存在的存储区)有一共同之处就是FILO —— 先入后出。但是数据结构中的堆和我们本文中的堆则是毫不相干。
果冻虾仁
2021/12/08
5580
C/C++:堆栈面面观
深入理解计算机系统(第三版)/ CSAPP 杂谈,第3章:程序的机器级表示
*x86-64还为128位操作提供有限支持,当imulq和mulq为双操作数时,是64位乘法;当为单操作数时,另一个乘数将视为%rax,而结果将存放在%rdx(高64位),%rax(低64位)中。
sickworm
2019/02/27
1.2K0
go语言调度器源代码情景分析之二:CPU寄存器
寄存器是CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果,之所以要使用寄存器来临时存放数据而不是直接操作内存,一是因为CPU的工作原理决定了有些操作运算只能在CPU内部进行,二是因为CPU读写寄存器的速度比读写内存的速度快得多。
阿波张
2019/06/24
1.2K0
虚拟内存探究 -- 第五篇:The Stack, registers and assembly code
这是虚拟内存系列文章的第五篇,也是最后一篇,目标是以不同的方式在实践中学习一些计算机基础知识。
coderhuo
2020/01/20
9560
虚拟内存探究 -- 第五篇:The Stack, registers and assembly code
一个编译参数引发的血案
提示:公众号展示代码会自动折行,建议横屏阅读 问题描述 前几天进行测试,发现一个神奇的现象:不加任何优化的版本与加了-O2参数的版本测试结果不一致! 主要代码类似以下例子: #include <stdio.h> #include <stdint.h> class Foo { public:  Foo() { printf("%s\n", "ctor"); }  ~Foo() { printf("%s\n", "dtor"); }  struct tm {    unsigned int s
腾讯数据库技术
2018/12/20
8950
一个编译参数引发的血案
相关推荐
C++:从技术实现角度聊聊RTTI
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验