C缺陷与陷阱 — 6 深入理解函数与库函数

news2024/11/19 21:21:42

目录

1 由变量声明到函数声明

2 strcpy和strcat

3 缓冲输出

4 返回整数的getchar函数

5 使用errno检测错误

6 库函数signal

7 更新顺序文件


1 由变量声明到函数声明

C语言变量声明由数据类型、变量名和可选的初始化值组成,用一个通用表达式来描述变量声明的结构,可以表示为:

<数据类型> <变量名> [= <初始化值>]

如下的变量声明,是*pf是一个浮点数,也就是说,pf是一个指向浮点数的指针:

float *pf;

进一步在声明中还可以进一步组合,因此:

float *pf(), (*h)();

表示*pf()与(*h)()是浮点表达式。因为()结合优先级高于*,*pf()也就是*(pf()),pf是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。

2 strcpy和strcat

strcpy和strcat是C语言中两个常用的字符串操作函数,它们分别用于复制和连接字符串。函数原型如下:

char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
  • strcpy: 将源字符串src复制到目标字符串dest中。如果dest已经包含数据,strcpy会覆盖这些数据。
  • strcat: 将源字符串src连接到目标字符串dest的末尾。strcat会在dest的末尾加上src的字符串内容。

3 缓冲输出

即时输出意味着每次调用输出函数时,数据都会立即被发送到目标(如终端或文件)。在C语言中,标准输出(stdout)默认是带缓冲的,缓冲输出方式可以提高性能,因为减少了系统调用的次数(每次系统调用都有一定的开销)。然而,这也可能导致数据在缓冲区中停留一段时间,直到缓冲区满或程序显式地刷新缓冲区。

但你可以通过特定的方式设置它为无缓冲模式。如果你需要即时输出,可以考虑以下几个方式:

  • 使用stderr标准错误输出流(stderr)通常是无缓冲的,适用于需要即时反馈的情况。
  • 手动刷新缓冲区:虽然这不是即时输出,但你可以通过fflush(stdout);手动刷新stdout缓冲区,以确保到目前为止的所有输出都被发送到目标。

下面的示例中使用fprintf(stderr, ...)来向stderr流写入数据。由于stderr通常是无缓冲的,所以每次调用fprintf时,数据都会立即显示在终端上。

#include <stdio.h>  
  
int main() {  
    // 使用stderr进行即时输出  
    for (int i = 0; i < 5; i++) {  
        fprintf(stderr, "即时输出: %d\n", i);  
        // 注意:stderr通常是无缓冲的,所以这里的输出会立即显示在终端上  
        
        // 模拟一些耗时操作  
        for (int j = 0; j < 1000000; j++) {  
            // 空操作,仅为了消耗时间  
        }  
    }  
  
    return 0;  
}

缓冲输出是C语言标准I/O库提供的一种机制,用于提高文件或控制台输出的效率。当程序使用printf、fprintf、puts等函数输出数据时,这些数据首先被存储在内存中的一个缓冲区中,而不是直接发送到输出设备(如屏幕或文件)。当缓冲区满、遇到换行符、或显式地刷新缓冲区(如使用fflush函数)时,缓冲区中的数据才会被一次性发送到输出设备。

下面的示例程序使用printf函数向stdout流写入数据。由于stdout是带缓冲的,所以输出可能不会立即显示在终端上,而是先存储在缓冲区中。

#include <stdio.h>  
  
int main() {  
    // 使用stdout进行缓冲区输出  
    for (int i = 0; i < 5; i++) {  
        // 注意:stdout是带缓冲的,所以这里的输出可能不会立即显示在终端上  
        printf("缓冲区输出: %d\n", i);  
        // 模拟一些耗时操作  
        for (int j = 0; j < 1000000; j++) {  
        }  
        // 如果需要立即看到输出,可以手动刷新stdout缓冲区  
        // fflush(stdout);  
    }  
  
    // 如果在循环结束后调用fflush,则所有缓冲的输出都会显示在终端上  
    fflush(stdout);  
  
    return 0;  
}

4 返回整数的getchar函数

getchar 函数是 C 语言标准库中的一个函数,用于从标准输入(通常是键盘)读取下一个可用的字符,原型定义在 <stdio.h> 头文件中。

getchar 返回的是一个 int 类型的值,而不是 char。getchar返回所有可能的字符值(通常是 0 到 127 或 0 到 255,取决于字符集)以及一个特殊的文件结束标志 EOF。EOF 通常定义为 -1。

#include <stdio.h>
main()
{
    char c;
    while((c = getchar()) != EOF)
        putchar(c);
}

5 使用errno检测错误

在C语言中,errno 是一个全局变量,用于报告库函数在执行过程中遇到的最近一个错误。当库函数遇到错误时,它们通常会设置 errno 为一个特定的错误码,这些错误码在 <errno.h> 头文件中定义。然而,需要注意的是,并不是所有的库函数都会设置 errno。

下面的示例使用 errno 来检测 fopen 函数在尝试打开文件时是否发生了错误:

#include <stdio.h>  
#include <errno.h>  
#include <string.h>  
  
int main() {  
    FILE *fp = fopen("nonexistentfile.txt", "r");  
    if (fp == NULL) {  
        // fopen 失败,检查 errno  
        switch (errno) {  
            case ENOENT:  
                printf("文件不存在。\n");  
                break;  
            case EACCES:  
                printf("没有权限打开文件。\n");  
                break;  
            default:  
                printf("打开文件时发生未知错误。\n");  
        }  
    } else {  
        // fopen 成功,处理文件...  
        fclose(fp);  
    }  
    return 0;  
}

下面的代码利用这一特性进行错误处理,似乎再清楚明白不过,然而却是错误的:

/*调用库函数*/
if(errno){
    /*处理错误*/
}  

出错原因在于,在库函数调用没有失败的情况下,并没有强制要求库函数一定要设置errno为0,这样ermo的值就可能是前一个执行失败的库函数设置的值。下面的代码作了更正,很可惜还是错误的:

errno = 0;
/*调用库函数*/
if(errno){
    /*处理错误*/
}

出错原因在于,库函数在调用成功时,既没有强制要求对errno清零,但同时也没有禁止设置errno为其它值。正确的使用errno的方式是在调用库函数后,应该首先检查该函数的返回值来确定是否发生了错误。如果函数返回值表明发生了错误,那么再检查 errno 以获取具体的错误代码。

6 库函数signal

signal 函数允许程序为特定的信号指定一个信号处理函数,当进程接收到该信号时,将调用指定的函数。函数是 C 语言中用于设置信号处理函数的库函数之一,它定义在 <signal.h>头文件中。函数原型如下:

#include <signal.h>  
void (*signal(int sig, void (*func)(int)))(int);
  • sig要处理的信号编号。这些信号编号在 <signal.h> 中定义,例如 SIGINT(通常由 Ctrl+C 触发)、SIGTERM(请求程序终止)、SIGSEGV(无效的内存引用)等。
  • func当接收到信号 sig 时要调用的函数指针。如果 func 是 SIG_IGN,则忽略该信号;如果 func 是 SIG_DFL,则使用信号的默认处理方式。

下面的程序示例使用 signal 函数来处理 SIGINT 信号(通常由 Ctrl+C 触发),当用户按下 Ctrl+C 时,会触发 SIGINT 信号,然后调用 signal_handler 函数。

#include <stdio.h>  
#include <signal.h>  
#include <stdlib.h>  
  
void signal_handler(int signum) {  
    printf("捕获到信号 %d\n", signum);  
    // 清理并关闭  
    // ...  
    // 退出程序  
    exit(signum);  
}  
  
int main () {  
    // 设置信号 SIGINT 和信号处理函数  
    signal(SIGINT, signal_handler);  
  
    while(1) {  
        printf("程序正在运行...\n");  
        sleep(1);  
    }  
  
    return 0;  
}

但是库函数 signal 在使用中存在一些问题和限制,这些主要源于信号处理的异步性质以及 signal 函数本身的设计。信号处理函数应该被设计为可重入的,因为当信号处理函数正在执行时,主程序或其他信号处理函数可能会被中断。然而,由于 signal 函数没有提供任何机制来确保信号处理函数的原子性或可重入性,因此编写安全的信号处理函数可能非常困难。因此在需要处理复杂信号或编写跨平台、多线程代码时,建议使用可移植性和线程安全性更好的sigaction 函数。

7 更新顺序文件

许多系统中的标准输入/输出库都允许程序打开一个文件,同时进行写入和读出的操作:

FILE *fp;
fp = fopen (file,"r+");

 上面的例子代码打开了文件名由变量file指定的文件,对于存取权限的设定表明程序希望对这个文件进行输入和输出操作。编程者也许认为,程序一旦执行上述操作完毕,就可以自由地交错进行读出和写入的操作。遗憾的是,事实总难遂人所愿,为了保持与过去不能同时进行读写操作的程序的向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。下面的程序片段似乎更新了一个顺序文件中选定的记录:

while(fread((char*)&rec, sizeof(rec), 1, fp) == 1){
    /*对rec执行某些操作*/
    if(/*rec必须被重新写入*/){
        fseek(fp,-(long)sizeof(rec),1);
        fwrite((char *)&rec,sizeof(rec),1,fp);
        fseek(fp,OL,1);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2243628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ClickHouse的介绍、安装、数据类型

1、介绍和安装 1.1、简介 ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;主要用于在线分析处理查询&#xff08;OLAP&#xff09;&#xff0c;能够使用SQL查询实时生成分析数据报告。 OLAP&…

基于AOA算术优化的KNN数据聚类算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于AOA算术优化的KNN数据聚类算法matlab仿真。通过AOA优化算法&#xff0c;搜索最优的几个特征数据&#xff0c;进行KNN聚类&#xff0c;同时对比不同个数特征下…

tcp 超时计时器

在 TCP&#xff08;传输控制协议&#xff09;中有以下四种重要的计时器&#xff1a; 重传计时器&#xff08;Retransmission Timer&#xff09; 作用&#xff1a;用于处理数据包丢失的情况。当发送方发送一个数据段后&#xff0c;就会启动重传计时器。如果在计时器超时之前没有…

《Probing the 3D Awareness of Visual Foundation Models》论文解析——多视图一致性

一、论文简介 论文讨论了大规模预训练产生的视觉基础模型在处理任意图像时的强大能力&#xff0c;这些模型不仅能够完成训练任务&#xff0c;其中间表示还对其他视觉任务&#xff08;如检测和分割&#xff09;有用。研究者们提出了一个问题&#xff1a;这些模型是否能够表示物体…

【论文阅读】WaDec: Decompiling WebAssembly Using Large Language Model

论文阅读笔记:WaDec: Decompiling WebAssembly Using Large Language Model 1. 来源出处 论文标题: WaDec: Decompiling WebAssembly Using Large Language Model作者: Xinyu She, Yanjie Zhao, Haoyu Wang会议: 39th IEEE/ACM International Conference on Automated Softwar…

【数字孪生】从Abaqus到Unity有限元应力云图

从abaqus到unity&#xff1a; 目录 1. 数据准备 1.1 abaqus中提取element rpt文件 element rpt文件格式&#xff1a; 1.2 abaqus中提取node rpt文件&#xff1a; node rpt文件格式&#xff1a; 2. python预处理以上数据&#xff1a; 2.1 提取node rpt中的节点坐标及应力…

一次需升级系统的wxpython安装(macOS M1)

WARNING: The scripts libdoc, rebot and robot are installed in /Users/用户名/Library/Python/3.8/bin which is not on PATH. 背景&#xff1a;想在macos安装Robot Framework &#xff0c;显示pip3不是最新&#xff0c;更新pip3后显示不在PATH上 参看博主文章末尾 MAC系统…

MySQL45讲 第二十五讲 高可用性深度剖析:从主备原理到策略选择

文章目录 MySQL45讲 第二十五讲 高可用性深度剖析&#xff1a;从主备原理到策略选择一、MySQL 主备基础原理&#xff08;一&#xff09;主备关系与数据同步&#xff08;二&#xff09;主备切换流程 二、主备延迟分析&#xff08;一&#xff09;主备延迟的定义与计算&#xff08…

跨越网络边界:IPv6与零信任架构的深度融合

2024年&#xff0c;工信部发布了《关于开展“网络去NAT”专项工作 进一步深化IPv6部署应用的通知》&#xff0c;加速了国内网络由IPv4向IPv6的转型步伐。未来&#xff0c;各行各业将逐步去NAT&#xff0c;逐步向IPv6迁移。在此过程中&#xff0c;网络安全解决方案和产品能力将面…

Linux—ln(link files)命令使用方法(How to create links on Linux)

Linux—ln&#xff08;link files&#xff09;命令使用方法 在 Linux 系统中工作时&#xff0c;需要在不同的目录中使用相同的文件时&#xff0c;不必在每个目录下都复制一份文件&#xff0c;这样不仅浪费磁盘空间&#xff0c;还会导致文件管理上的混乱。 ln(link files) 便是…

我要成为算法高手-位运算篇

目录 1. 判断字符是否唯一2. 消失的数字3. 两整数之和4. 只出现一次的数字II5. 消失的两个数字 前情提要&#xff1a;如果对一些常见的二进制位运算不熟悉&#xff0c;请看这篇文章&#xff1a; 常见的位运算 1. 判断字符是否唯一 面试题 01.01. 判定字符是否唯一 - 力扣&…

1Panel 推送 SSL 证书到阿里云、腾讯云

本文首发于 Anyeの小站&#xff0c;点击链接 访问原文体验更佳 前言 都用 CDN 了还在乎那点 1 年证书钱么&#xff1f; 开句玩笑话&#xff0c;按照 Apple 的说法&#xff0c;证书有效期不该超过 45 天。那么证书有效期的缩短意味着要更频繁地更新证书。对于我这样的“裸奔”…

23种设计模式-访问者(Visitor)设计模式

文章目录 一.什么是访问者模式&#xff1f;二.访问者模式的结构三.访问者模式的应用场景四.访问者模式的优缺点五.访问者模式的C实现六.访问者模式的JAVA实现七.代码解释八.总结 类图&#xff1a; 访问者设计模式类图 一.什么是访问者模式&#xff1f; 访问者模式&#xff08;…

JavaScript——DOM编程、JS的对象和JSON

一、DOM编程 DOM(Document Object Model)编程&#xff1a;就是使用document对象的API&#xff0c;完成对网页HTML文档进行动态修改&#xff0c;以实现网页数据&#xff0c;和样式动态变化效果的编程。 (一)DOM获取元素的多种方法 1.查找元素的函数 getElementById("id值…

Pr:音频过渡

Adobe Premiere Pro 自带一组共三个音频过渡 Audio Transitions效果。 对音频剪辑之间应用交叉淡化 Crossfade过渡&#xff0c;操作方式类似于应用视频过渡效果。 对于交叉淡化&#xff0c;要保证前剪辑的出点之后及后剪辑的入点之前有足够的预留内容&#xff08;也称“手柄”&…

大数据-226 离线数仓 - Flume 优化配置 自定义拦截器 拦截原理 拦截器实现 Java

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…

stm32启动过程解析startup启动文件

1.STM32的启动过程模式 1.1 根据boot引脚决定三种启动模式 复位后&#xff0c;在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值。BOOT0 为专用引脚&#xff0c;而 BOOT1 则与 GPIO 引脚共用。一旦完成对 BOOT1 的采样&#xff0c;相应 GPIO 引脚即进入空闲状态&#xff0c;可用于…

如何在项目中用elementui实现分页器功能

1.在结构部分复制官网代码&#xff1a; <template> 标签: 这是 Vue 模板的根标签&#xff0c;包含所有的 HTML 元素和 Vue 组件。 <div> 标签: 这是一个普通的 HTML 元素&#xff0c;包裹了 el-pagination 组件。它没有特别的意义&#xff0c;只是为了确保 el-pagi…

15-大模型 RAG 经验篇

一、LLMs 已经具备了较强能力了&#xff0c;存在哪些不足点? 在 LLM 已经具备了较强能力的基础上&#xff0c;仍然存在以下问题&#xff1a; 幻觉问题&#xff1a;LLM 文本生成的底层原理是基于概率的 token by token 的形式&#xff0c;因此会不可避免地产生"一本正经…

数据结构-二叉树及其遍历

🚀欢迎来到我的【数据结构】专栏🚀 🙋我是小蜗,一名在职牛马。🐒我的博客主页​​​​​​ ➡️ ➡️ 小蜗向前冲的主页🙏🙏欢迎大家的关注,你们的关注是我创作的最大动力🙏🙏🌍前言 本篇文章咱们聊聊数据结构中的树,准确的说因该是只说一说二叉树以及相…