98-模拟 errno

这一篇,让我们搞懂 errno 的实现原理,不过为了防止名字冲突,我们换一个名字,叫 myerrno.

1. 思路

讲一下用到的技术吧:

本质上 errno 并不是一个真正意义上的变量,而是通过宏定义扩展为语句,而这一行语句实际上是在调用函数,该函数返回保存了指向 errno 变量的指针。

这里我们直接看程序就行了。

2. 程序清单

2.1 代码

// myerrno.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

// 实际上 myerrno 就是一个宏定义
#define myerrno (*_myerrno())

pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;

// 使用 pthread once 对键进行初始化
void thread_init() {
  puts("I'm thread_init");
  pthread_key_create(&key, free); // 这里注册了析构函数就是 free
}

// 该函数用来获取真正的 myerrno 的地址
int *_myerrno() {
  int *p; 
  pthread_once(&init_done, thread_init);
  // 如果根据键拿到的是一个空地址,说明之前还未分配内存
  p = (int*) pthread_getspecific(key);
  if (p == NULL) {
    p = (int*)malloc(sizeof(int));
    pthread_setspecific(key, (void*)p);
  }
  /**************************************/
  return p;
}

void* fun1() {
  errno = 5;
  myerrno = 5; // 这一行被扩展成 (*_myerrno()) = 5
  sleep(1);
  // printf 后面的 myerrno 会被扩展成 (*_myerrno())
  printf("fun1: errno = %d, myerrno = %d\n", errno, myerrno);
  return NULL;
}

void* fun2() {
  errno = 10; 
  myerrno = 10;  // 这一行被扩展成 (*_myerrno()) = 10
  printf("fun2: errno = %d, myerrno = %d\n", errno, myerrno);
  return NULL;
}

int main() {
  pthread_t tid1, tid2;
  pthread_create(&tid1, NULL, fun1, NULL);
  pthread_create(&tid2, NULL, fun2, NULL);
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  return 0;
}

2.2 编译和运行

  • 编译和运行
$ gcc myerrno.c -o myerrno -lpthread
$ ./myerrno
  • 运行结果


这里写图片描述
图1 运行结果

图1 的结果也正是我们期望的。上面的程序也很容易读懂,关键技术在于宏定义上的一个小 trick,这里就不再分析了。

再看一下 bits/errno.h 中的 errno 就知道,它也是这么做的:


这里写图片描述
图2 errno 变量,在多线程环境中就是一个宏定义

图 2 中的 __errno_location 函数就相当于我们程序的 _myerrno 函数。

3. 总结

  • 理解 errno 并不是真正意义上的变量
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值