当前位置: 首页 > news >正文

C缺陷与陷阱 — 7 可移植性缺陷

目录

1 应对C语言标准变更

2 标识符的名称限制

3 整数的大小

4 字符是有符号整数还是无符号整数

5 移位运算符

6 内存位置0

7 除法运算时发生的截断


1 应对C语言标准变更

使用新特性可以使代码更容易编写且减少错误,但可能会导致代码在旧编译器上无法编译。square函数是一个简单的数学函数,用于计算一个数的平方。在新风格中,函数原型明确指定了参数类型,如下所示:

double square(double x) {return x * x;
}

如果这样写,这个函数在很多编译器上都不能通过编译。如果我们,ANSI标准为了保持和以前的用法兼容,按照旧风格来重写这个函数,这就增强了它的可移植性。但在旧风格中,函数原型不包含参数类型(这里只举例说明,旧标准现在基本不再使用),如下所示:

double square(x) double x;  // 函数参数声明
{return x * x;  // 计算x的平方并返回结果
}

这种可移植性为了与旧用法保持一致,我们必须在调用了square函数的程序中作如下声明:

double square() 

但是函数square的声明中并没有对参数类型做出说明,因此在编译main函数时,编译器无法得知函数square的参数类型应该是double还是其他类型。如下面的示例,函数调用将会报错。

double square();
main()
{printf("g\n",square(3));
}

为避免这类问题,可在声明中带入参数类型,3会被自动转换为double类型:

double square(double):
main()
{printf("g\n",square(3));
}

另一种改写的方式是,在这个程序中显式地给函数square传入一个double类型的参数:

double square(double x); // 显式指定参数类型
main() {printf("%g\n", square(3.0)); // 显式传入double类型的参数
}

2 标识符的名称限制

在C语言的不同实现中,对标识符的处理方式存在差异。一些实现会接受标识符中的所有字符,而另一些实现可能会截断过长的标识符。连接器对外部名称也有特定的限制,例如可能只允许使用大写字母。ANSI C标准规定,C语言实现至少能够区分外部名称的前6个字符,且不区分大小写。

因此,为了确保程序的可移植性,选择外部标识符的名称时需要谨慎。例如,不应使用容易混淆的名称,如print_fields和print_float,或者State和STATE。例如下面的示例代码:

// 定义一个函数 Malloc
char Malloc(unsigned n) {char *p;char *malloc(unsigned); // 声明 malloc 函数的原型p = malloc(n); // 调用 malloc 函数尝试分配 n 字节的内存if (p == NULL) // 如果 malloc 分配失败,返回 NULLpanic("out of memory"); return p; // 如果分配成功,返回分配的内存的指针
}

这个程序的意图是在需要分配内存的地方调用Malloc函数,而不是直接调用malloc。如果malloc失败,panic函数会被调用,终止程序并打印错误消息。这样,客户程序就不需要在每次调用malloc时都进行检查。

然而,如果编译环境是不区分大小写的C语言实现,那么函数malloc和Malloc可能会被视为相同,导致调用Malloc时实际上是在递归调用自己。在这种情况下,程序在第一次尝试分配内存时对Malloc函数的调用将引起一系列递归调用,而这些递归调用没有返回点,最终导致程序崩溃。

3 整数的大小

C语言提供了三种不同长度的整数类型:short、int 和 long,以及字符类型。这些类型的长度有以下特点:

  • 长度是非递减的,即 short ≤ int ≤ long。
  • 普通整数(int 类型)足够大,可以容纳任何数组下标。
  • 字符长度由硬件决定,现代大多数机器的字符长度是8位,但有些实现中字符长度可能是16位。

ANSI标准规定 long 至少32位,short 和 int 至少16位。这些规定意味着我们不能依赖于具体的位数,但可以保证一定的最小长度。

在编程实践中,这意味着我们不能依赖于具体的精度,而应该根据需要选择合适的类型。例如,如果需要存储可能达到千万数量级的数值,最好声明为 long 类型。

4 字符是有符号整数还是无符号整数

在C语言中,char 类型可以是无符号的或有符号的,这取决于编译器的实现。大多数现代编译器将字符实现为8位整数。

  • 字符的符号性:当需要将字符值转换为较大的整数时,字符是有符号还是无符号的变得重要。如果字符是有符号的,转换为 int 时符号位会扩展;如果是无符号的,则高位会填充0。
  • 字符的取值范围:如果字符被视为有符号,其取值范围是 -128 到 127;如果被视为无符号,则取值范围是 0 到 255。

为了确保字符被视为无符号整数,程序员可以声明 unsigned char。这样,无论在什么编译器上,字符在转换为整数时多余的位都会被填充为0。

与此相关的一个常见错误认识是:如果c是一个字符变量,使用(unsigned) c就可得到与c等价的无符号整数。这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果。正确的方式是使用语句(unsigned char) c,因为一个unsigned char类型的字符在转换为无符号整数时无需首先转换为int型整数,而是直接进行转换。

#include <stdio.h>int main() {char signedChar = -1; // 有符号字符unsigned char unsignedChar = 255; // 无符号字符// 打印字符的整数值printf("Signed char as int: %d\n", signedChar);printf("Unsigned char as int: %u\n", unsignedChar);// 正确转换有符号字符为无符号整数printf("Correctly converted signed char as unsigned int: %u\n", (unsigned char)signedChar);return 0;
}

5 移位运算符

使用移位运算符的程序员经常对这样两个问题感到困惑:

(1)向右移位时的填充问题

  • 对于无符号数,空出的位由0填充。
  • 对于有符号数,C语言实现可能用0或符号位的副本填充。

如果程序员关心向右移位时空出的位,可以将变量声明为无符号类型,这样空出的位都会被设置为0。

(2)移位计数的取值范围:

  • 移位计数必须大于等于0,且小于被移位对象的位数。
  • 这个限制确保了移位操作可以在硬件上高效实现。

例如,对于32位的整数,n << 31 和 n << 0 是合法的,而 n << 32 和 n << -1 是不合法的。

(3)移位和除法不完全等同

即使在某些C语言实现中,有符号整数的右移会用符号位填充新位,这也不等同于除以2的幂。例如,(-1) >> 1 的结果通常不为0,但 1 / 2 在大多数C实现中结果为0。

在C语言中,对于有符号整数,-1 >> 1 的操作结果取决于整数的位数和计算机的架构。对于一个32位的整数:

-1 在二进制中通常表示为一个32位的全1的模式,即 11111111 11111111 11111111 11111111。当你将 -1 向右移动1位时,根据补码规则,最左边会填充1(符号位扩展),结果仍然是 -1。因此,在大多数现代计算机上,-1 >> 1 的结果是 -1。

如果已知 low + high 为非负,那么:mid = (low + high) >> 1; 与 mid = (low + high) / 2; 完全等效,但前者的执行速度要快得多。

6 内存位置0

空指针不指向任何对象,使用它除了赋值或比较外都是非法的。例如,使用空指针进行strcmp操作会导致未定义行为,不同编译器结果可能不同。

  • 某些编译器对内存地址0有硬件级的读保护,使用空指针会导致程序立即终止。
  • 有些编译器允许读但不允许写内存地址0,空指针看似指向字符串,但内容可能是无意义的“垃圾信息”。
  • 还有些编译器允许读写内存地址0,错误使用空指针可能导致覆盖操作系统内容,造成严重问题。

在所有C程序中,错误使用空指针都是未定义的,但可能在某些编译器上“看似”能工作,直到换到另一台机器上才会出现问题。

检查这类问题的一个方法是将程序移到不允许读取内存地址0的机器上运行。以下是一个示例程序,用于检测C语言实现如何处理内存地址0:

#include <stdio.h>int main() {char *p = NULL;printf("Location 0 contains %d\n", *p);return 0;
}

在禁止读取内存地址0的机器上,这个程序会失败。在其他机器上,它将打印出内存位置0中存储的字符内容。

7 除法运算时发生的截断

假定我们让q = a /b,r = a % b,商为q,余数为r,在整数除法和余数运算中,我们希望满足以下三条性质:

  1. 定义余数的关系a == qb+r
  2. 符号变化:改变a的正负号应改变q的符号,但不改变q的绝对值。
  3. 余数范围:当b>0时,希望保证0≤ r <b。这对于使用余数作为哈希表索引等场景很重要。

然而,这三条性质不可能同时满足。考虑一个简单的例子:3/2,商为1,余数也为1。此时,第1条性质满足。(-3)/2的值应该是多少呢?如果要满足第2条性质,答案应该是-1,但如果是这样,余数就必定是-1,这样第3条性质就无法满足。如果我们首先满足第3条性质,即余数是1,这种情况下根据第1条性质则商是-2,那么第2条性质又无法满足了。

因此,C语言或者其他语言在实现整数除法截断运算时,必须放弃上述三条原则中的至少一条。大多数程序设计语言选择了放弃第3条,而改为要求余数与被除数的正负号相同。这样,性质1和性质2就可以得到满足。

然而,C语言的定义只保证了性质1,以及当a>=0且b>0时,保证 |r|<b以及r>=0.后面部分的保证与性质2或者性质3比较起来,限制性要弱得多。

C语言的定义虽然有时候会带来不需要的灵活性,但大多数时候,这个定义对让整数除法运算满足其需要来说还是够用了的。

相关文章:

C缺陷与陷阱 — 7 可移植性缺陷

目录 1 应对C语言标准变更 2 标识符的名称限制 3 整数的大小 4 字符是有符号整数还是无符号整数 5 移位运算符 6 内存位置0 7 除法运算时发生的截断 1 应对C语言标准变更 使用新特性可以使代码更容易编写且减少错误&#xff0c;但可能会导致代码在旧编译器上无法编译。…...

应急响应:玄机_Linux后门应急

https://xj.edisec.net/challenges/95 11关做出拿到万能密码&#xff0c;ATMB6666&#xff0c;后面都在root权限下操作 1、主机后门用户名称&#xff1a;提交格式如&#xff1a;flag{backdoor} cat /etc/passwd&#xff0c;发现后门用户 flag{backdoor} 2、主机排查项中可以…...

C++:捕获 shared_from_this()和捕获this的区别

两种方法的主要区别在于对象的生命周期管理以及捕获方式的不同。以下是对两种方法的详细对比&#xff1a; 第一种&#xff1a;捕获 shared_from_this() 的方法 event.subscribe([self shared_from_this()]() {std::cout << "Event triggered, object is alive.&qu…...

网络协议之TCP

一、定义 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;由IETF的RFC 793定义。TCP旨在适应支持多网络应用的分层协议层次结构。在因特网协议族&#xff08;Internet p…...

《澳鹏AI全景报告2024》分析最新的数据挑战

华盛顿州柯克兰市&#xff0c;2024 年 10 月 22 日 —— Appen Limited&#xff08;澳大利亚证券交易所代码&#xff1a;APX&#xff09;&#xff0c;一家为人工智能生命周期提供高质量数据的领先供应商&#xff0c;发布了其《2024 年人工智能现状报告》。该报告对美国多个行业…...

【Java每日面试题】—— String、StringBuilder和StringBuffer的区别?

1、String 不可变性:String对象创建后不可变,内容不能被修改,对字符串修改会产生一个新的字符串对象。 线程:线程安全 适用:字符串内容不发生变化或少量字符串操作 String str = "Hello"; str = str + " World"; 2、StringBuffer 不可变性:对…...

【设计模式】【创建型模式(Creational Patterns)】之单例模式

单例模式是一种常用的创建型设计模式&#xff0c;其目的是确保一个类只有一个实例&#xff0c;并提供一个全局访问点。 单例模式的原理 单例模式的核心在于控制类的实例化过程&#xff0c;通常通过以下方式实现&#xff1a; 私有化构造函数&#xff0c;防止外部直接实例化。…...

form表单的使用

模板 <template><el-form :model"formData" ref"form1Ref" :rules"rules"><el-form-item label"手机号" prop"tel"><el-input v-model"formData.tel" /></el-form-item><el-f…...

PDF内容提取,MinerU使用

准备环境 # python 3.10 python3 -m pip install huggingface_hub python3 -m pip install modelscope python3 -m pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com下载需要的模型 import json import osimport requests from huggingface_hub…...

SpringCloud篇(服务网关 - GateWay)

目录 一、简介 二、为什么需要网关 二、gateway快速入门 1. 创建gateway服务&#xff0c;引入依赖 2. 编写启动类 3. 编写基础配置和路由规则 4. 重启测试 5. 网关路由的流程图 6. 总结 三、断言工厂 四、过滤器工厂 1. 路由过滤器的种类 2. 请求头过滤器 3. 默认…...

自动化测试之unittest框架详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 unittest 1、什么是Unittest框架&#xff1f; python自带一种单元测试框架 2、为什么使用UnitTest框架&#xff1f; >批量执行用例 >提供丰富的断…...

Vue3 provide 和 inject的使用

在 Vue 中&#xff0c;provide 和 inject 是 Composition API 的一对功能&#xff0c;用于父子组件之间的依赖注入。它们的作用是让父组件可以向其所有子组件提供数据或方法&#xff0c;而不需要通过逐层传递 props。 1. provide provide 用于父组件中&#xff0c;提供数据或…...

掌握Git分布式版本控制工具:从基础到实践

一、引言 在软件开发过程中&#xff0c;版本控制是不可或缺的一环。Git作为一种分布式版本控制工具&#xff0c;以其高效、灵活的特点&#xff0c;受到了广大开发者的青睐。本文将详细介绍Git的基本概念、工作流程、常用命令&#xff0c;以及在IntelliJ IDEA中的操作方法。 二、…...

AndroidStudio与开发板调试时连接失败或APP闪退的解决方案,涉及SELINUX及获取Root权限

现象 用AndroidStudio打开工程代码,点击运行后,报错: 解决方案 具体原因是尝试运行 su(通常用于获取超级用户权限)时失败了,提示 “Permission denied” 通过 CONFIG_SECURITY_SELINUX 变量控制 SElinux 开启或关闭 在vim /rk3568_android_sdk/device/rockchip/rk…...

VMWARE虚拟交换机的负载平衡算法

一、基于源虚拟端口的路由 虚拟交换机可根据 vSphere 标准交换机或 vSphere Distributed Switch 上的虚拟机端口 ID 选择上行链路。 基于源虚拟端口的路由是 vSphere 标准交换机和 vSphere Distributed Switch 上的默认负载平衡方法。 ESXi主机上运行的每个虚拟机在虚拟交换…...

安卓InputDispatching Timeout ANR 流程

1 ANR的检测逻辑有两个参与者: 观测者A和被观测者B&#xff0c;当然&#xff0c;这两者是不在同一个线程中的。2 A在调用B中的逻辑时&#xff0c;同时在A中保存一个标记F&#xff0c;然后做个延时操作C&#xff0c;延时时间设为T&#xff0c;这一步称为: 埋雷 。3 B中的逻辑如果…...

【Nginx从入门到精通】03 、安装部署-让虚拟机可以联网

文章目录 总结一、配置联网【Minimal 精简版】1.1、查看网络配置1.2、配置ip地址 : 修改配置文件 <font colororange>ifcfg-ens33Stage 1&#xff1a;输入指令Stage 2&#xff1a;修改参数Stage 3&#xff1a;重启网络Stage 4&#xff1a;测试上网 二、配置联网【Everyth…...

java 增强型for循环 详解

Java 增强型 for 循环&#xff08;Enhanced for Loop&#xff09;详解 增强型 for 循环&#xff08;也称为 “for-each” 循环&#xff09;是 Java 从 JDK 5 开始引入的一种便捷循环语法&#xff0c;旨在简化对数组或集合类的迭代操作。 1. 基本语法 语法格式 for (类型 变量…...

浪潮云启操作系统(InLinux) bcache宕机问题分析

前言 本文以一次真实的内核宕机问题为切入点&#xff0c;结合实际操作案例&#xff0c;详细展示了如何利用工具 crash对内核转储&#xff08;kdump&#xff09;进行深入分析和调试的方法。通过对崩溃日志的解读、函数调用栈的梳理、关键地址的定位以及代码逻辑的排查&#xff…...

038集——quadtree(CAD—C#二次开发入门)

效果如下&#xff1a; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.T…...

备赛蓝桥杯--算法题目(1)

1. 链表求和 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode *head nullptr, *tail nullptr;int carry 0;while (l1 || l2) {int n1 l1 ? l1->val: 0;int n2 l2 ? l2->val:…...

机器学习100道经典面试题库(二)

机器学习100道经典面试题库&#xff08;31-60&#xff09; 在大规模的语料中&#xff0c;挖掘词的相关性是一个重要的问题。以下哪一个信息不能用于确定两个词的相关性。 A、互信息 B、最大熵 C、卡方检验 D、最大似然比 答案&#xff1a;B 解析&#xff1a;最大熵代表了…...

Unet++改进37:添加KACNConvNDLayer(2024最新改进方法)

本文内容:添加KACNConvNDLayer 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 1.步骤一 新建block/kacn_conv.py文件,添加如下代码: import torch import torch.nn as nn##源码地址:https://github.com/SynodicMonth/ChebyKAN class KACNConvNDLaye…...

基于 Levenberg - Marquardt 法的 BP 网络学习改进算法详解

基于 Levenberg - Marquardt 法的 BP 网络学习改进算法详解 一、引言 BP&#xff08;Back Propagation&#xff09;神经网络在众多领域有着广泛应用&#xff0c;但传统 BP 算法存在收敛速度慢、易陷入局部最优等问题。Levenberg - Marquardt&#xff08;LM&#xff09;算法作…...

MySQL 8.0与PostgreSQL 15.8的性能对比

根据搜索结果&#xff0c;以下是MySQL 8.0与PostgreSQL 15.8的性能对比&#xff1a; MySQL 8.0性能特点&#xff1a; MySQL在处理大量读操作时表现出色&#xff0c;其存储引擎InnoDB提供了行级锁定和高效的事务处理&#xff0c;适用于并发读取的场景。MySQL通过查询缓存来提高读…...

qt连接postgres数据库时 setConnectOptions函数用法

连接选项&#xff0c;而这些选项没有直接的方法对应&#xff0c;你可能需要采用以下策略之一&#xff1a; 由于Qt SQL API的限制&#xff0c;你可能需要采用一些变通方法或查阅相关文档和社区资源以获取最新的信息和最佳实践。如果你确实需要设置特定的连接选项&#xff0c;并且…...

MySQL45讲 第二十七讲 主库故障应对:从库切换策略与 GTID 详解——阅读总结

文章目录 MySQL45讲 第二十七讲 主库故障应对&#xff1a;从库切换策略与 GTID 详解一、一主多从架构与主备切换的挑战&#xff08;一&#xff09;一主多从基本结构&#xff08;二&#xff09;主备切换的复杂性 二、基于位点的主备切换&#xff08;一&#xff09;同步位点的概念…...

JavaWeb笔记整理——Spring Task、WebSocket

目录 SpringTask ​cron表达式 WebSocket SpringTask cron表达式 WebSocket...

基于SpringBoot+RabbitMQ完成应⽤通信

前言&#xff1a; 经过上面俩章学习&#xff0c;我们已经知道Rabbit的使用方式RabbitMQ 七种工作模式介绍_rabbitmq 工作模式-CSDN博客 RabbitMQ的工作队列在Spring Boot中实现&#xff08;详解常⽤的⼯作模式&#xff09;-CSDN博客作为⼀个消息队列,RabbitMQ也可以⽤作应⽤程…...

Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行

问题 IOS14设备&#xff0c;切后台划掉&#xff0c;二次启动崩溃。 原因 IOS14以上 flutter 不支持debugger模式下的二次启动 。 要二次启动需要以release方式编译工程安装至手机。 操作步骤 清理项目&#xff1a;在命令行中运行flutter clean来清理之前的构建文件。重新构…...

怎么建com的网站/广州网站建设推广专家

我想得到按流量来排序&#xff0c;而且还是倒序&#xff0c;怎么达到实现呢&#xff1f; 达到下面这种效果&#xff0c; 默认是根据key来排&#xff0c; 我想根据value里的某个排&#xff0c; 解决思路:将value里的某个&#xff0c;放到key里去&#xff0c;然后来排 下面&#…...

顺的品牌网站建设/品牌营销推广方案怎么做

# 周围可能有东西能帮到你。 # 首先&#xff0c;移动到橱柜。 hero.moveUp() hero.moveRight(2) hero.moveDown(2) # 然后&#xff0c;使用while-true循环攻击"Cupboard"&#xff08;橱柜&#xff09;。 while True: hero.attack("Cupboard")...

城乡建设学校官方网站/站外推广渠道

卓老师&#xff0c;我有一个信号与系统的问题想请教。按照时域采样定理&#xff0c;采样频率≥2倍的信号频率&#xff0c;才能得到信号全部信息。而以智能车中的编码器测速为例。我们知道测速周期在可接受范围内越小越利于控速&#xff0c;比如2ms。但2ms采样一次速度&#xff…...

旅游网站设计的目的/深圳市企业网站seo营销工具

日本某地发生了一件谋杀案&#xff0c;警察通过排查确定杀人凶手必为4个嫌疑犯 的一个。以下为4个嫌疑犯的供词。 A说&#xff1a;不是我。 B说&#xff1a;是C。 C说&#xff1a;是D。 D说&#xff1a;C在胡说 已知3个人说了真话&#xff0c;1个人说的是假话。 现在请根据这些…...

站长工具最近查询/广东企业网站seo哪里好

2019独角兽企业重金招聘Python工程师标准>>> Logback日志使用说明 项目跑了几个月了、测试服务器一直报空间不足、公司很小也没有运维。登上去查看以后发现log日志文件上百G并且很多都是没用的。所以下午简单查看了一下日志文件的配置。 没有优化后的logback.xml配置…...

个人网站建设策划书/app推广方案怎么写

项目生命周期&#xff1a; &#xff08;清除项目编译信息-clean&#xff09;-- 编译(compile) -- 测试(test) -- 打包(package) -- 安装(install) -- 部署(deploy) clean&#xff1a;清理生命周期 compile ---> deploy&#xff1a;默认生命周期 site&#xff1a;站点生命…...