热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

但是结果总错是什么原因

本文主要介绍关于c语言,开发语言,嵌入式开发,arm开发的知识点,对【在ARM64下编程的常见陷阱:C语言常见陷阱】和【但是结果总错是什么原因】有兴趣的朋友可以看下由【人邮异步社区】投稿的技术文章,希

本文主要介绍关于c语言,开发语言,嵌入式开发,arm开发的知识点,对【在ARM64下编程的常见陷阱:C语言常见陷阱】和【但是结果总错是什么原因】有兴趣的朋友可以看下由【人邮异步社区】投稿的技术文章,希望该技术和经验能帮到你解决你所遇的相关技术问题。

但是结果总错是什么原因

在ARM32下通常采用ILP32数据模型,而在ARM64下可以采用LP64和ILP64数据模型。在Linux系统下默认采用LP64数据模型,在Windows系统下采用ILP64数据模型。在64位机器上,若int类型是32位,long类型为64位,指针类型也是64位,那么该机器就是LP64的。其中,L表示Long,P表示Pointer。而ILP64表示int类型是32位,long类型是32位,long long类型是64位,指针类型是64位。ILP32、ILP64、LP64数据模型中不同数据类型的长度如表21.1所示。

表21.1 ILP32、ILP64、LP64数据模型中不同数据类型的长度

在32位系统里,由于整型和指针长度相同,因此某些代码会把指针强制转换为int或unsigned int来进行地址运算。

【例21-1】在下面的代码中,get_pte()函数根据PTE基地址pte_base和offset来计算PTE的地址,并转成指针类型。

1    char * get_pte(char *pte_base, int offset)
2    {
3        int pte_addr, pte;
4 
5        pte_addr = (int)pte_base;
6        pte = pte_addr + offset;
7 
8        return (char *)pte;
9    }

第5行使用int类型把pte_base指针转换成地址,在32位系统中没有问题,因为int类型和指针类型都占用4字节。但是在64位系统中就有问题了,int类型占4字节,而指针类型占8字节。在跨系统的编程中,推荐使用C99标准定义intptr_t和uintptr_t类型,根据系统的位数来确定二者的大小。

示例代码如下。

#if __WORDSIZE == 64
     typedef long int                intptr_t;
     typedef unsigned long int       uintptr_t;
#else
     typedef int                     intptr_t;
     typedef unsigned int            uintptr_t;
#endif

上述代码可以修改成以下形式。

pte_addr = (intptr_t)pte_base;

在Linux内核中,通常内存地址使用unsigned long来转换,这利用了指针和长整型的字节大小是相同的这个事实。

【例21-2】下面的代码利用了指针和长整型的字节大小是相同的这个事实,来实现类型转换。

1    unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
2    {
3        struct page *page;
4 
5        page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);
6        if (!page)
7            return 0;
8        return (unsigned long) page_address(page);
9    }

在第8行中,把page的指针转换成地址,使用unsigned long,这样保证了在32位系统和64位系统中都能正常工作。

2 数据类型转换与整型提升

C语言有隐式的数据类型转换,它很容易出错。下面是隐式数据类型转换的一般规则。

在赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型。在算术表达式中,占字节少的数据类型向占字节多的数据类型转换,如图21.1所示。例如,在ARM64系统中,当对int类型和long类型的值进行运算时,int类型的数据需要转换成long类型。

▲图21.1 数据类型转换

在算术表达式中,当对有符号数据类型与无符号数据类型进行运算时,需要把有符号数据类型转换为无符号数据类型。例如,若表达式中既有int类型又有unsigned int类型,则所有的int类型数据都被转化为unsigned int类型。整数常量通常是int类型。例如,在ARM64系统里,整数8会使用W n寄存器来存储,8LL则会使用X n寄存器来存储。

【例21-3】在下面的代码中,最终输出值是多少?

1    #include 
  
    2 3 void main() 4 { 5 unsigned int i = 3; 6 7 printf("0x%x\n", i * -1); 8 }
  

首先−1是整数常量,它可以用int类型表达,而变量i是unsigned int类型。根据上述规则,当对int类型和unsigned int类型数据进行计算时,需要把int类型转换成unsigned int类型。所以,数据−1转成unsigned int类型,即0xFFFFFFFF。表达式“i −1”变成“3 0xFFFFFFFF”,计算结果会溢出,最后变成0xFFFFFFFD。

C语言规范中有一个整型提升(integral promotion)的约定。

在表达式中,当使用有符号或者无符号的char、short、位域(bit-field)以及枚举类型时,都应该提升到int类型。如果上述类型可以使用int类型来表示,则使用int类型;否则,使用unsigned int类型。

整型提升的意义是,使CPU内部的ALU充分利用通用寄存器的长度,例如,ARM64处理器的通用寄存器支持32位宽和64位宽,而int类型和unsigned int类型正好是32位宽。对于两个char类型值的运算,CPU难以直接实现字节相加的运算,在CPU内部要先转换为通用寄存器的标准长度。在ARM64处理器里,通用寄存器最小的标准长度是32位,即4字节。因此,两个char类型值需要存储到32位的Wn通用寄存器中,然后再进行相加运算。

【例21-4】在下面的代码中,abc的值分别是多少?

1    #include 
  
    2 3 void main() 4 { 5 char a; 6 unsigned int b; 7 unsigned long c; 8 9 a = 0x88; 10 b = ~a; 11 c = ~a; 12 13 printf("a=0x%x, ~a=0x%x, b=0x%x, c=0x%lx\n", a, ~a, b, c); 14 }
  

在QEMU+ARM64系统中,运行结果如下。

benshushu:mnt# ./test
a=0x88, ~a=0xffffff77, b=0xffffff77, c=0xffffffffffffff77

有读者认为~a的值应该为0x77,但是根据整型提升的规则,表达式“~a”会转换成int类型,所以最终值为0xFFFFFF77。

C语言里还有一个符号扩展问题,当要把一个带符号的整数提升为同一类型或更长类型的无符号整数时,它首先被提升为更长类型的带符号等价值,然后转换为无符号值。

【例21-5】在下面的代码中,最终输出值分别是多少?

1    #include 
  
    2 3 struct foo { 4 unsigned int a:19; 5 unsigned int b:13; 6 }; 7 8 void main() 9 { 10 struct foo addr; 11 12 unsigned long base; 13 14 addr.a = 0x40000; 15 base = addr.a <<13; 16 17 printf("0x%lx, 0x%lx\n", addr.a <<13, base); 18 }
  

addr.a为位域类型,根据整型提升的规则,它首先会被提升为int类型。表达式“addr.a <<13” 的类型为int类型,但是未发生符号扩展。在给base赋值时,根据带符号和无符号整数提升规则,会先转换为long,然后转换为unsigned long。从int转换为long时,会发生符号扩展。

上述程序最终的执行结果如下。

benshushu:mnt# ./test
0x80000000, 0xffffffff80000000

如果想让base得到正确的值,可以先把addr.a从int类型转换成unsigned long类型。

base = (unsigned long)addr.a <<13;

【例21-6】在下面的代码中,最终输出值是多少?

#include 
  
    void main() { unsigned char a = 0xa5; unsigned char b = ~a>>4 + 1; printf("b=%d\n", b); }
  

在表达式“~a>>4 + 1”中,按位取反的优先级最高,因此首先计算“~a”表达式。根据整型提升的规则,a被提升到int类型,最终得到0xFFFFFF5A。加法的优先级高于右移运算的优先级,表达式变成0xFFFFFF5A >> 5,得到0xFFFFFFFA。最终b的值为0xFA,即250。

3 移位操作

在C语言中,移位操作是很容易出错的地方。整数常量通常被看成int类型。如果移位的范围超过int类型,那么就会出错了。

【例21-7】下面的代码片段有什么问题?

#include 
  
    void main() { unsigned long reg = 1 <<33; printf("0x%lx\n", reg); }
  

上面代码片段在编译过程中会提示如下编译警告。

benshushu:mnt# gcc test.c -o test
test.c: In function ‘main’:
test.c:5:24: warning: left shift count >= width of type [-Wshift-count-overflow]
    5 |  unsigned long reg = 1 <<33;
      |                        ^~

虽然编译能通过,但是程序执行结果不正确。因为整数常量1被看成int类型,在ARM64处理器中会使用Wn寄存器来存储。若左移33位,则超过了Wn寄存器的范围。正确的做法是使用“1ULL”,这样编译器会这个整数常量看成unsigned long long类型,在ARM64处理器内部使用Xn寄存器。正确的代码如下。

unsigned long reg = 1ULL <<33;
以上内容摘自《ARM64体系结构编程与实践》

基于树莓派4B开发板系统介绍arm64体系结构,讲解通俗,大量实战经验与高频面试题,丰富资源随书赠送,更有配套教学视频助您快速上手。

本书旨在详细介绍ARM64体系结构的相关技术。本书首先介绍了ARM64体系结构的基础知识、搭建树莓派实验环境的方法,然后讲述了ARM64指令集中的加载与存储指令、算术与移位指令、比较与跳转等指令以及ARM64指令集中的陷阱,接着讨论了GNU汇编器、链接器、链接脚本、GCC内嵌汇编代码、异常处理、中断处理、GIC-V2,最后剖析了内存管理、高速缓存、缓存一致性、TLB管理、内存屏障指令、原子操作、操作系统等内容。

本书适合嵌入式开发人员阅读。

本文《在ARM64下编程的常见陷阱:C语言常见陷阱》版权归人邮异步社区所有,引用在ARM64下编程的常见陷阱:C语言常见陷阱需遵循CC 4.0 BY-SA版权协议。


推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
author-avatar
null100
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有