首页 > 其他分享 >简单练习

简单练习

时间:2024-11-02 11:41:39浏览次数:2  
标签:函数 中断 练习 内存 简单 interrupt NULL ptr

const、static、volatile

解释关键字static,const,volatile各自的含义及其作用,并且解释下面这个函数有什么错误?
int square(volatile int *ptr)
{
	return *ptr * *ptr;
}
1. static:
   - **用于局部变量**:如果 `static` 修饰一个局部变量,该变量的生命周期会延长至程序的整个运行过程,而不仅仅是在函数调用期间存在。局部静态变量在第一次进入该函数时初始化,以后不再重新初始化,因此在每次调用时保留上一次的值。
   - **用于全局变量或函数**:如果 `static` 修饰一个全局变量或函数,该变量或函数的作用域将被限制在定义所在的文件中。这样做可以防止外部文件访问该变量或调用该函数,达到封装的效果。

2. const:
   - 用于声明一个只读变量,即一旦初始化,变量的值就不能再被修改。如果试图直接修改 `const` 变量的值,编译器会报错。
   - `const` 的作用在于明确意图,使变量不可变,提高代码的安全性和可读性。通过指针间接修改 `const` 变量的值在某些场景是可能的,但这种用法通常不建议。

3. volatile:
   - 用于告诉编译器该变量可能随时被意外地修改(例如由其他线程或硬件修改)。编译器通常会将变量缓存到寄存器中以提高访问速度,但如果变量被 `volatile` 修饰,编译器将放弃优化,每次访问该变量时都会从内存中重新读取,保证获取的是变量的最新值。
   - `volatile` 关键字多用于硬件寄存器、共享变量或信号处理程序等场景,在这些场景中,变量的值可能在程序控制之外发生改变。

### 函数分析

来看给定的函数:


int square(volatile int *ptr)
{
    return *ptr * *ptr;
}

函数的问题在于对 *ptr 的值进行了两次读取。因为 *ptr 是一个 volatile 变量,编译器不会缓存它的值。然而,由于 *ptr 的值可能在第一次读取和第二次读取之间发生变化,导致两次读取的结果可能不同,从而使函数的返回值不可靠。

错误示例说明

例如,如果 *ptr 在第一次读取后被其他程序或硬件改变,导致 *ptr 的值在两次读取间不一致,*ptr * *ptr 的结果就不是单一值的平方,而是两个不同值的乘积,产生不准确的结果。

解决方法

为确保 square 函数返回一致的结果,可以先将 *ptr 的值存入一个临时变量,然后对该值进行平方运算:

int square(volatile int *ptr)
{
    int temp = *ptr;
    return temp * temp;
}

在这里,*ptr 只被读取一次,保证了计算的稳定性。如果 *ptr 的值在运算过程中被修改,不会影响到已经保存到 temp 中的值。


关于宏定义

请解释下面这段语句的意思及区别?
#define dPS struct s *
typedef struct s * tPS;
dPS p1,p2;
tPS p3,p4;
  1. 宏定义#define dPS struct s* 定义了一个宏,dPS 只是一个文本替换。每当编译器遇到 dPS 时,它会用 struct s* 替换。这是在预处理阶段完成的。

  2. 类型别名typedef struct s* tPs; 这里使用 typedefstruct s* 创建了一个新的类型别名 tPs。与宏不同,typedef 是在编译阶段处理的,创建的是类型的别名,而不是简单的文本替换。

  3. 定义指针

    • dPS p1, p2; 被替换为 struct s* p1, p2;,表示 p1p2 是指向 struct s 的指针。
    • tPS p3, p4; 这行定义了 p3p4struct s* 类型的指针,等同于 struct s* p3, p4;

总结:

  • 宏定义是纯文本替换,而 typedef 是在编译时创建类型别名。
  • 宏可能引入错误(如替换不当),而 typedef 更安全,因为它提供了类型的语义。

中断问题

__interrupt double compute_area(double radius)
{
    double area = PI * radius * radius;
    printf("\nArea = %d", area);
    return area;
}

代码分析

  1. __interrupt
    __interrupt 通常是用于嵌入式系统中的关键字,标记一个函数为中断处理程序(ISR, Interrupt Service Routine)。在标准C语言中没有 __interrupt 关键字,因此此处使用 __interrupt 是不合法的。在一般编译器中会产生错误。如果想要计算面积,不需要将此函数声明为中断处理函数,直接去掉 __interrupt 即可。

  2. PI
    这里代码使用了 PI 常量,但它并未在代码中定义。通常,我们会在代码中定义 #define PI 3.14159,或通过 math.h 头文件中的 M_PI 来使用 PI 的值。这会导致编译时错误。

  3. printf("\nArea = %d", area);

    • %d 格式符用于输出整数类型,而 areadouble 类型,这样的格式不匹配会导致输出结果错误甚至崩溃。
    • 正确的格式符应使用 %f。如果想保留两位小数,可以使用 %.2f,例如:printf("\nArea = %.2f", area);

代码的错误总结

  1. 不合法的 __interrupt 关键字。
  2. PI 未定义。
  3. printf 中的格式符 %d 不匹配 double 类型,应改为 %.2f

修正后的代码

修正后,代码可以如下:

#include <stdio.h>
#define PI 3.14159  // 定义 PI

double compute_area(double radius)
{
    double area = PI * radius * radius;
    printf("\nArea = %.2f", area);
    return area;
}

修正后代码的解释

  1. PI 使用宏定义赋值为 3.14159,或者可以包含 <math.h> 并使用 M_PI
  2. 删除了 __interrupt 关键字。
  3. 使用了正确的格式符 %.2f 来输出 double 类型,并保留两位小数。
tips:

设计中断服务函数(ISR, Interrupt Service Routine)时,需要特别注意以下几个关键点,以确保中断处理的高效性和稳定性:

  1. 简短而高效
  • 中断服务函数应尽可能短小、快速。因为中断会打断主程序的执行,长时间占用 CPU 资源可能导致系统无法及时响应其他重要事件。
  • 避免复杂的逻辑和大量计算,应只处理最关键的任务,如标记事件、读写数据、或触发后续处理。
  1. 避免使用阻塞操作
  • 在 ISR 中应避免使用 delaysleep 等阻塞性操作。中断服务函数不适合等待外部资源,避免死锁或超时情况。
  • 尽量避免任何会导致等待的代码(例如,可能阻塞的 I/O 操作)。
  1. 避免使用标准库函数
  • 一些标准库函数(如 printfmalloc)可能在中断上下文中不安全,可能导致不可预知的结果或程序崩溃。
  • 例如,printf 通常会有较长的执行时间,可能会干扰系统的实时性,mallocfree 则可能导致内存碎片或占用较多时间。
  1. 确保线程安全
  • 中断服务函数通常在系统的不同上下文下执行,因此必须防止访问共享数据时的竞争条件。
  • 使用 volatile 关键字修饰共享变量,确保每次访问时都会从内存读取最新值,避免编译器优化造成的旧数据使用。
  • 在某些架构下,为了保护共享资源,可以使用锁或禁用全局中断(但禁用时间应尽可能短)。
  1. 避免嵌套中断
  • 如果嵌套中断无法避免,应确保优先级设置正确,防止优先级较低的中断长时间被阻塞。
  • 嵌套中断会增加系统复杂度,增加调试难度,因此在设计中尽量避免过多的嵌套。
  1. 尽量少用全局变量
  • 尽量减少全局变量的使用。如果使用全局变量,必须特别注意多线程或多中断的情况下的资源保护。
  • 可以通过设置标志位等方式,通知主程序在非中断上下文中进一步处理。
  1. 保存并恢复上下文
  • 在进入中断时,应保存当前寄存器状态,以便中断结束时可以恢复。这在中断可能导致程序数据混乱或异常跳转时尤为重要。
  • 现代编译器和硬件可能自动完成上下文保存,但在特定低级别的开发中,手动保存和恢复上下文很重要。
  1. 使用合适的中断优先级
  • 对于多重中断情况,分配合理的中断优先级很重要。更重要或时间紧迫的中断应设置更高优先级。
  • 正确设置中断优先级可以防止重要中断被较长的低优先级中断阻塞。
  1. 确保 ISR 可重入
  • 在某些情况下,中断服务函数可能会被再次触发,因此 ISR 应设计为可重入(reentrant),即即使多次调用,也不会引发数据冲突或错误。
  • 尽量避免在 ISR 中调用非重入的函数或操作。
  1. 尽量避免返回值
  • 中断服务函数通常不应返回值,因为返回值不会被主程序或调用函数使用。在一些系统中,ISR 的返回值可能会被忽略。
  • 可以使用标志位或队列来通知主程序中断处理结果。

示例:简化的中断服务函数

// 假设标志位 interrupt_occurred 通知主程序处理中断
volatile int interrupt_occurred = 0;

void __interrupt ISR(void) {
    // 设定标志位,通知主程序处理中断
    interrupt_occurred = 1;
}

在主程序中可以检测 interrupt_occurred 标志位,在非中断上下文中做进一步处理。这种方法保持 ISR 的简洁性,减少了在中断中进行复杂操作的需求。


内存问题

在嵌入式系统中,动态分配内存可能引发一些问题,主要包括以下几点:

  1. 内存碎片
  • 嵌入式系统中的内存通常有限,频繁的动态内存分配和释放会导致内存碎片化。碎片化会导致即使有足够的总内存,也无法满足某次分配的需求,导致分配失败。
  1. 内存泄漏
  • 如果动态分配的内存没有被正确释放,会导致内存泄漏。这在嵌入式系统中是严重的问题,因为资源有限,系统长时间运行后可能会耗尽可用内存,导致系统崩溃或异常。
  1. 实时性问题
  • 动态内存分配的时间不确定,分配或释放内存时,分配器可能需要查找空闲块或进行合并操作,导致执行时间不可预测。这在实时系统中可能会影响任务的及时性。
  1. 分配失败
  • 如果内存不足,malloc 调用可能会返回 NULL。在嵌入式系统中,这种情况可能导致程序行为异常,尤其是如果没有对 NULL 返回值进行检查的情况下。

代码分析与输出结果

  1. malloc(0) 行为
    调用 malloc(0) 时,根据 C 标准,不一定返回 NULL,具体行为依赖于实现:

    • 某些实现会返回一个非 NULL 的指针,但这块指针指向的内存不可访问(即不能使用)。
    • 其他实现可能直接返回 NULL
  2. 比较运算
    (ptr = (char *)malloc(0)) == NULL 判断 malloc(0) 是否返回 NULL。如果返回 NULL,则输出 "Got a null pointer";否则输出 "Got a valid pointer"

代码输出

  • 如果 malloc(0) 返回 NULL,则输出:Got a null pointer
  • 如果 malloc(0) 返回一个有效指针,输出:Got a valid pointer

注意:该代码的实际输出取决于具体编译器和标准库的实现。在一些系统上可能返回 NULL,而在另一些系统上则可能返回有效指针。


请用 C编写一个 ASC码字符串拷贝程序。

/*
 * @Author: [email protected]
 * @Date: 2024-11-02
 * @LastEditors: None
 * @LastEditTime: None
 * @FilePath: C语言编写一个ASC码字符串copy.c
 * @Description:None
 * 
 * Copyright (c) 2024 by [email protected], All Rights Reserved. 
 */
 
#include <stdio.h>
#include <stdlib.h>

// 自定义字符串拷贝函数,拷贝 ASCII 字符串
char* Mystrcpy(const char *source) {
    // 检查输入的源字符串是否为 NULL
    if (source == NULL) {
        printf("输入字符串为空!\n");
        return NULL;
    }
    
    // 计算源字符串的长度
    int len = 0;
    const char *temp = source;
    while (*temp++) {
        len++;
    }
    
    // 为新字符串分配内存,包含一个额外的空间用于存储终止符 '\0'
    char *copy = (char *)malloc((len + 1) * sizeof(char));
    if (copy == NULL) {
        printf("内存分配失败!\n");
        return NULL;
    }
    
    // 使用指针逐字节拷贝字符串内容
    char *dest = copy;
    while ((*dest++ = *source++));  // 逐个字符拷贝,包含终止符 '\0'
    
    return copy;  // 返回拷贝后的字符串的起始地址
}

int main() {
    char source[] = "Hello, ASCII!";
    char *copiedString = Mystrcpy(source);
    
    if (copiedString != NULL) {
        printf("拷贝后的字符串: %s\n", copiedString);
        free(copiedString);  // 释放动态分配的内存
    }

    return 0;
}

标签:函数,中断,练习,内存,简单,interrupt,NULL,ptr
From: https://www.cnblogs.com/hhail08/p/18521752

相关文章

  • 练习时长两天半vue
    <scriptsetuplang="ts">import{computed,ref}from"vue";import{getScjhService,getWorkerService,getProcessService,addWorkInfoService}from"@/api/fprw.js";constscjh=ref({generalOrder:'',divi......
  • LeetCode题练习与总结:矩形区域不超过 K 的最大数值和--363
    一、题目描述给你一个 mxn 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。题目数据保证总会存在一个数值和不超过 k 的矩形区域。示例1:输入:matrix=[[1,0,1],[0,-2,3]],k=2输出:2解释:蓝色边框圈出来的矩形区域 [[......
  • LeetCode题练习与总结:水壶问题--365
    一、题目描述有两个水壶,容量分别为 x 和 y 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 target 升。你可以:装满任意一个水壶清空任意一个水壶将水从一个水壶倒入另一个水壶,直到接水壶已满,或倒水壶已空。示例1: 输入:x=3,y=5,target=4输出......
  • 手把手教你学PCIE(12.1)--开发一个简单的 PCIe 设备驱动程序
    目录手把手教你学PCIe实战实例目标环境准备步骤一:理解PCIe基本概念步骤二:设置开发环境步骤三:开发PCIe设备驱动程序步骤四:测试和调试驱动程序步骤五:深入学习和实践结语PCIe(PeripheralComponentInterconnectExpress)是一种高速串行计算机扩展总线标准,广泛用......
  • 手把手教你学PCIE(12.2)--开发一个简单的 PCIe 设备驱动程序
    目录PCIe实战实例:开发一个简单的PCIe设备驱动程序目标环境准备步骤一:理解PCIe基本概念步骤二:设置开发环境步骤三:开发PCIe设备驱动程序步骤四:测试和调试驱动程序步骤五:深入学习和实践结语PCIe实战实例来展示如何开发一个简单的PCIe设备驱动程序,该驱动程序......
  • 【Kaggle | Pandas】练习5:数据类型和缺失值
    文章目录1.获取列数据类型.dtype/.dypes2.转换数据类型.astype()3.获取数据为空的列.isnull()4.将缺少值替换并且排序.fillna(),.sort_values()1.获取列数据类型.dtype/.dypes数据集中points列的数据类型是什么?#Yourcodeheredtype=reviews.points.d......
  • 真题练习37-Excel电子表格-全国计算机等级考试一级计算机基础及MS Office应用考试【汪
    第37组请根据题目要求,完成下列操作:打开考生文件夹下的电子表格,按照下列要求完成对此文稿的修饰并保存。1.在考生文件夹下打开EXCEL.XLSX文件:(1)将sheet1工作表的A1:G1单元格合并为一个单元格,内容水平居中;计算2015年和2016年产品销售总量分别置于B15和D15单元格内,分别计算2015......
  • C++写一个简单的JSON解析
    参考用C++编写一个简易的JSON解析器(1)写一个动态类型-知乎欢迎测试和反馈bug首先,json包含string,number,integer,object,array,bool,null这些类型对于object映射,使用map,对于array使用vector我们定义一个类Val用来存储,使用variant来存储具体的值std::variant-cppreferen......
  • 牛客软件开发专项练习-Day2
    1.下列叙述中正确的是(A)A.顺序存储结构的存储一定是连续的,链式存储结构的存储空间不一定是连续的B.顺序存储结构只针对线性结构,链式存储结构只针对非线性结构C.顺序存储结构能存储有序表,链式存储结构不能存储有序表D.链式存储结构比顺序存储结构节省存储空间解释:链式存储结......
  • 牛客软件开发专项练习-Day4
    1.下面关于并行和并发的区别,说法错误的是(C)A.并发计算是一种程序计算的形式,在系统中,至少有两个以上的计算在同时运作,计算结果可能同时发生B.并行计算指许多指令得以同时进行的计算模式。在同时进行的前提下,可以将计算的过程分解成小部份,之后以并发方式来加以解决C.并行是同时......