errno 和 strerror函数

news2024/11/23 13:29:53

        今天写了一个很简单的代码,编译时没啥错误和警告(主要编译选项没开启警告),然后运行时居然 segmentation fault,把我给看傻了,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
    int fd = open("readme",  O_RDWR);
    if(fd < 0)
    {
        printf("open fail: %s\n", strerror(errno));
        return fd;
    }

    return 0;
}

代码简单吧,就把 errno 对应的字符串打印出来,结果就 Segmentation fault 了,搞得我以为用法不对呢,我还去 man 了一个 strerror() 函数的用法:

 

用 %s 打印 char * 类型,是对的啊,怎么会这样呢?搞得我还去看了源码,顺便学习了一下 errno,原因后面再讲到。先看一下函数 strerror() 的源码:

/* Copyright (C) 1991-2022 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <https://www.gnu.org/licenses/>.  */
#include <string.h>
#include <locale/localeinfo.h>
char *
strerror (int errnum)
{
  return __strerror_l (errnum, __libc_tsd_get (locale_t, LOCALE));
}

再看 __strerror_l  函数的源码:

/* Return a string describing the errno code in ERRNUM.  */
char *
__strerror_l (int errnum, locale_t loc)
{
  int saved_errno = errno;
  char *err = (char *) __get_errlist (errnum);
  if (__glibc_unlikely (err == NULL))
    {
      struct tls_internal_t *tls_internal = __glibc_tls_internal ();
      free (tls_internal->strerror_l_buf);
      if (__asprintf (&tls_internal->strerror_l_buf, "%s%d",
		      translate ("Unknown error ", loc), errnum) == -1)
	tls_internal->strerror_l_buf = NULL;
      err = tls_internal->strerror_l_buf;
    }
  else
    err = (char *) translate (err, loc);
  __set_errno (saved_errno);
  return err;
}

 接着往下看 __get_errlist() 这个函数:

#ifndef ERR_MAP
# define ERR_MAP(n) n
#endif
const char *const _sys_errlist_internal[] =
  {
#define _S(n, str)         [ERR_MAP(n)] = str,
#include <errlist.h>
#undef _S
  };
const char *
__get_errlist (int errnum)
{
  int mapped = ERR_MAP (errnum);
  if (mapped >= 0 && mapped < array_length (_sys_errlist_internal))
    return _sys_errlist_internal[mapped];
  return NULL;
}

最后就是直接返回对应的指针数组元素值,这个指针数组初始化方式可以参考上一篇文件 这里 。定义了宏 #define _S(n, str)         [ERR_MAP(n)] = str 其实就是 _S(n,str) 被替换为 [n]=str,即为元素初始化,而在 errlist.h 头文件里使用了该宏:

_S(0, N_("Success"))
#ifdef EPERM
/*
TRANS Only the owner of the file (or other resource)
TRANS or processes with special privileges can perform the operation. */
_S(EPERM, N_("Operation not permitted"))
#endif
#ifdef ENOENT
/*
TRANS This is a ``file doesn't exist'' error
TRANS for ordinary files that are referenced in contexts where they are
TRANS expected to already exist. */
_S(ENOENT, N_("No such file or directory"))
#endif
#ifdef ESRCH
/*
TRANS No process matches the specified process ID. */
_S(ESRCH, N_("No such process"))
#endif
#ifdef EINTR

/*往下还有很多,这里就不完整展现了*/

而这些 EPERM、ENOENT、ESRCH 等待,就是 errno 的值,这里初始化了 errno 对应的错误字符串,所以经过预编译,_sys_errlist_internal 数组的值应该类似这样:

const char *const _sys_errlist_internal[] =
{
    [0] = "Success",
    [1] = "Operation not permitted",
    [2] = "No such file or directory",
    [3] = "No such process",
    [4] = "Interrupted system call",
    /*.....*/
};
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define	EPERM		 1	/* Operation not permitted */
#define	ENOENT		 2	/* No such file or directory */
#define	ESRCH		 3	/* No such process */
#define	EINTR		 4	/* Interrupted system call */
#define	EIO		 5	/* I/O error */
#define	ENXIO		 6	/* No such device or address */
#define	E2BIG		 7	/* Argument list too long */
#define	ENOEXEC		 8	/* Exec format error */
#define	EBADF		 9	/* Bad file number */
#define	ECHILD		10	/* No child processes */
#define	EAGAIN		11	/* Try again */
#define	ENOMEM		12	/* Out of memory */
#define	EACCES		13	/* Permission denied */
#define	EFAULT		14	/* Bad address */
#define	ENOTBLK		15	/* Block device required */
#define	EBUSY		16	/* Device or resource busy */
#define	EEXIST		17	/* File exists */
#define	EXDEV		18	/* Cross-device link */
#define	ENODEV		19	/* No such device */
#define	ENOTDIR		20	/* Not a directory */
#define	EISDIR		21	/* Is a directory */
#define	EINVAL		22	/* Invalid argument */
#define	ENFILE		23	/* File table overflow */
#define	EMFILE		24	/* Too many open files */
#define	ENOTTY		25	/* Not a typewriter */
#define	ETXTBSY		26	/* Text file busy */
#define	EFBIG		27	/* File too large */
#define	ENOSPC		28	/* No space left on device */
#define	ESPIPE		29	/* Illegal seek */
#define	EROFS		30	/* Read-only file system */
#define	EMLINK		31	/* Too many links */
#define	EPIPE		32	/* Broken pipe */
#define	EDOM		33	/* Math argument out of domain of func */
#define	ERANGE		34	/* Math result not representable */
#endif

到这里我们已经知道函数 strerror() 是怎么实现的了,而且也知道函数使用方法也是对的,但为什么会出现 Segmentation fault 呢?原因是没包含头文件,导致 strerror() 函数变成隐式声明了,而隐式声明的函数它的默认返回值是 int 类型(没找到C语言关于这点的说明文档,有同学知道的话烦告知一下),所以用 %s 打印 int 类型导致 segmentation fault 了。所以改用 %d 来打印还会出现段错误吗?

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

const char *const _sys_errlist_internal[] =
{
    [0] = "Success",
    [1] = "Operation not permitted",
    [2] = "No such file or directory",
    [3] = "No such process",
    [4] = "Interrupted system call",
    /*.....*/
};
int main()
{
    int fd = open("readme",  O_RDWR);
    if(fd < 0)
    {
        printf("open fail: %d\n", strerror(errno));
        return fd;
    }
    
    return 0;
}

正确的做法:

1,编译时添加 -Wall 把所有的警告都输出

2,添加头文件 #inlcude <string.h>

因为没做第一步,编译完全看不出问题,假如做了第一步,编译警告可提供更多的线索,如:

gcc 警告信息也是相当重要的啊,要是直接忽略还得花时间查半天,还搞得莫名其妙^-^ 

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

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

相关文章

华为认证网络工程师学习笔记:AAA原理与配置

对于任何网络&#xff0c;用户管理都是最基本的安全管理要求之一。 AAA&#xff08;Authentication, Authorization, and Accounting&#xff09;是一种管理框架&#xff0c;它提供了授权部分用户访问指定资源和记录这些用户操作行为的安全机制。因其具有良好的可扩展性&#…

右值引用(rvalue reference)

定义 C11 引入了右值引用&#xff08;rvalue reference&#xff09;的概念&#xff0c;这是为了支持移动语义&#xff08;move semantics&#xff09;和完美转发&#xff08;perfect forwarding&#xff09;而引入的新特性。右值引用允许我们高效地处理临时对象&#xff0c;避…

运维知识点-hibernate引擎-HQL

HQL有两个主要含义&#xff0c;分别是&#xff1a; HQL&#xff08;Hibernate Query Language&#xff09;是Hibernate查询语言的缩写&#xff0c;它是一种面向对象的查询语言&#xff0c;类似于SQL&#xff0c;但不是去对表和列进行操作&#xff0c;而是面向对象和它们的属性…

Tech.co推荐:小型企业必备的5款财务管理软件

创业不易、守业更难。对于刚起步的小企业来说&#xff0c;财务管理也是拦路虎之一。除了财务团队建设、内部监管的加强&#xff0c;工具使用也必不可少。从趋势上来看&#xff0c;企业的财务数字化转型是必经之路&#xff0c;不过对于小企业来说&#xff0c;在谈数字化转型之前…

STM32 GPIO的几种工作模式

介绍STM32 GPIO的几种工作模式 1、输出模式 STM32的引脚输出有两种方式&#xff1a; 1、推挽输出 2、开漏输出 1.1 推挽输出 当引脚设置为推挽输出时&#xff0c;P-MOS和N-MOS共同配合工作。 当使用HAL库 //该函数的作用就是将P-MOS导通&#xff0c;N-MOS关…

FPGA- RGB_TFT显示屏原理及驱动逻辑

下图是TFT显示屏的显示效果 该显示屏共分为 2 个版本&#xff0c;4.3 寸版本的 TFT4.3’’_V3.0 和 5.0 寸版本的 TFT5.0’’_V3.0。 两者 PCB 背板电路完全相同&#xff0c;接口脚位定义完全相同&#xff0c;接口时序完全相同&#xff0c;仅使用的显示屏 模组尺寸不同。设计两…

chromedriverUnable to obtain driver for chrome using ,selenium找不到chromedriver

1、下载chromedriver chromedriver下载网址&#xff1a;CNPM Binaries Mirror 老版本在&#xff1a;chromedriver/ 较新版本在&#xff1a;chrome-for-testing/ 2、设置了环境变量还是找不到chromedriverUnable to obtain driver for chrome using NoSuchDriverException:…

IDEA修改git提交者的信息

git提交后&#xff0c;idea会记录下提交人的信息&#xff0c;如果不修改提交人信息的话&#xff0c;会有一个默认值。避免每次提交都要填提交人信息&#xff0c;直接设置成自己想要的默认值&#xff0c;该怎么操作&#xff1f; 提交的时候在这里修改提交人信息 避免每次都去设置…

小白优化Oracle的利器”sqltrpt.sql”脚本

SQL调优顾问是Oracle自带的一个功能强大的内部诊断工具&#xff0c;用于对性能不佳的SQL语句给出优化建议。但如果从命令行调用它比较麻烦&#xff0c;幸运的是&#xff0c;Oracle提供了一个方便的内置脚本“sqltrpt.sql”&#xff0c;简化了调用过程。 sqltrpt.sql脚本位于Or…

【论文速读】 | AI驱动修复:漏洞自动化修复的未来

本次分享论文为&#xff1a;AI-powered patching: the future of automated vulnerability fixes 基本信息 原文作者&#xff1a;Jan Nowakowski, Jan Keller 作者单位&#xff1a;Google Security Engineering 关键词&#xff1a;AI, 安全性漏洞, 自动化修复, LLM, sanitiz…

C++初阶篇----类与对象下卷

目录 1.再谈析构函数1.1构造函数体赋值1.2 初始化列表1.3 explicit关键字 2.Static成员2.1概念2.2 特性 3.友元3.1 概念3.2友元函数3.3 友元类 4.内部类4.1 概念 5.匿名对象5.1 概念 6.拷贝对象时的一些编译器优化7.再次理解封装 1.再谈析构函数 1.1构造函数体赋值 在对类的实…

力扣大厂热门面试算法题 - 动态规划

爬梯子、跳跃游戏、最小路径和、杨辉三角、接雨水。每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.05 可通过leetcode所有测试用例。 目录 70. 爬楼梯 解题思路 完整代码 Python Java 55. 跳跃游戏 解题思路 完整代码 Python 代码…

LeetCode每日一题 二叉树的最大深度(二叉树)

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;root [1,nul…

大摩突发:将推出比特币ETF

作者&#xff1a;秦晋 随着比特币ETF愈发火爆&#xff0c;华尔街另一家管理1.3万亿美元资产的大型经纪自营商「摩根士丹利」正在蠢蠢欲动&#xff0c;准备进军比特币ETF。 据彭博社数据显示&#xff0c;目前10只比特币现货ETF在上周三创下单日交易新纪录&#xff0c;成交量超过…

太惊艳了!多微信管理利器,让你事半功倍!

作为现代社交媒体的主要平台之一&#xff0c;微信在商务领域中扮演着重要的角色。为了提高我们的工作效率&#xff0c;微信管理系统应运而生。 这个系统可以同时登录多个微信账号&#xff0c;并进行统一管理。除了便捷的登录管理功能外&#xff0c;微信管理系统还提供了许多实…

优思学院|质量和企业的盈利能力有何关系?

质量和企业的盈利能力有何关系&#xff1f;三十年前&#xff0c;这个问题就已经被提出。当时的学者们研究了高质量产品如何带来更高的盈利。虽然这听起来像是老生常谈&#xff0c;但它的真理至今仍深深影响着我们的商业决策。 为了更直观地理解&#xff0c;一些学者绘制了以下…

Redis 核心面试题归纳

文章目录 RedisAOF 相关1. redis AOF 文件备份时&#xff0c;是使用的 write ahead log 的方式吗2. redis 开启AOF后的写入步骤3. redis AOF文件重写过程4.AOF 持久化策略 RDB 相关1.RDB 写入过程rdb 过程中&#xff0c;复制的页表是什么 Redis 主从同步1.PSYNC 和 SYNC 的区别…

Vue 前端开发 v-for和v-if两个指令不能混合使用

原由&#xff1a; 在进行项目开发的时候因为在一个标签上同时使用了v-for和v-if两个指令导致的报错。 提示错误&#xff1a;The undefined variable inside v-for directive should be replaced with a computed property that returns filtered array instead. You should no…

安装QT时,安装进程(qt.tools.perl)运行期间出现错误

安装QT时&#xff0c;安装进程(qt.tools.perl)运行期间出现错误 解决方法

小智浏览器助手

作为使用者来说&#xff0c;这个浏览器头痛的地方就是不能随意的切换地址&#xff0c;每次都要重新配置ini文件 再重新打开。 于是&#xff0c;我想了个办法&#xff0c;在使用前面加个能切换&#xff0c;维护地址的程序&#xff0c;让它来调用这个浏览器不就实现我的要求了&a…