【C语言】strerror函数和malloc函数

简介: 我的第一门语言就是C,但是学艺不精,中途跑去学了C#和Java后,感觉到了C的重要性,毕竟是最接近底层的语言,又跑回来学C。毕竟前两门的控制语句,变量什么的都是类似的,回到C后只需要学习一些特定C的语法,比如宏,预编译指令等等,这些对我来说都是陌生的词汇。前来记录一下陌生的东西。

前言


我的第一门语言就是C,但是学艺不精,中途跑去学了C#和Java后,感觉到了C的重要性,毕竟是最接近底层的语言,又跑回来学C。


毕竟前两门的控制语句,变量什么的都是类似的,回到C后只需要学习一些特定C的语法,比如宏,预编译指令等等,这些对我来说都是陌生的词汇。


前来记录一下陌生的东西。



一、strerror函数


库函数调用失败的时候会产生错误码,而每一个错误码对应着一条错误信息,strerror函数的作用就是将错误码给转化成错误信息。


在C语言中有一条全局的错误码errno,在程序运行过程中,只要库函数调用失败,我们就会把此处产生的错误码放入变量errno中。


该函数的原型为:

char *strerror(int errnum);


其中,errnum表示操作系统返回的错误码,该函数返回一个指向错误信息字符串的指针。


例如,当调用文件操作函数时出现错误时,可以使用strerror函数获取相应的错误信息:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void)
{
    FILE *fp;
    fp = fopen("non_existent_file.txt", "r");
    if (fp == NULL) {
        printf("Error opening file: %s", strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}


在上面的例子中,如果打开一个不存在的文件,则会出现错误,并返回一个非零值。


使用strerror函数能够获取到对应的错误信息,类似于能访问errno全局变量的的函数(我是这么理解的),并输出给用户。需要注意的是,在使用strerror函数前必须包含头文件errno.h和string.h。



1)线程局部存储概念


线程局部存储变量(Thread-Local Storage variable),简称TLS变量,是一类特殊的变量,它具有全局变量的生命周期,但是只能被线程本身访问。这意味着每个线程都拥有自己独立的变量副本,并且该变量在不同线程中的值互相独立


在C11标准中,可以使用关键字_Thread_local来定义一个线程局部存储变量。例如:

#include <stdio.h>
#include <threads.h>
_Thread_local int local_var;
int my_thread_func(void* arg)
{
    // 在每个线程中操作自己独立的 local_var 副本
    local_var = 42;
    printf("Thread local variable: %d", local_var);
    return 0;
}
int main()
{
    thrd_t tid1, tid2;
    thrd_create(&tid1, my_thread_func, NULL);
    thrd_create(&tid2, my_thread_func, NULL);
    thrd_join(tid1, NULL);
    thrd_join(tid2, NULL);
    return 0;
}


在上面的代码中,每个线程内部使用 _Thread_local 关键字声明一个名为 local_var 的线程局部存储变量。然后在主函数中创建两个线程并分别执行 my_thread_func 函数,在这个函数中修改本地副本 local_var 的值,并打印出它的值。


需要注意,由于每个线程都拥有自己独立的变量副本,因此不同线程修改的 local_var 值是相互独立的,不会相互影响。


2)线程局部存储变量errno


C语言中的 errno 变量是一个全局变量,它用于保存最近一次系统调用或库函数调用发生的错误代码(错误码)。


当发生系统调用或库函数调用出错时,相关的函数通常会设置 errno 变量并返回特定值来指示发生了什么错误。


为了避免多线程程序中对于 errno 变量的竞争访问,C11标准中新增了对于线程局部存储变量的支持。


这样,每个线程都可以拥有自己独立的 errno 变量副本,从而避免了多线程之间对 errno 的竞争访问。


在使用 TLS 版本的 errno 时,需要先使用 extern 关键字声明外部引用,并使用关键字 _Thread_local 说明其为线程局部存储变量。例如:

#include <stdio.h>
#include <threads.h>
extern _Thread_local int errno;
int main(void)
{
    // 各个线程操作自己独立的 errno 值
    thrd_create(...);
    thrd_join(...);
    return 0;
}


需要注意的是,在大多数系统上,标准库已经将 errno 定义为宏,在不同平台下该行为可能有所不同。建议使用 中定义的常量来代替具体值。


另外需要注意不要修改任何标准库函数设置的 errno 值,因为这可能会影响其他函数的行为。通常来说,只有在需要自己处理错误码时才会直接操作 errno 的值。


strerror疑问


如果同个线程同时发生了两个不同的错误,同时放入全局变量中errno中,打印出来的errno会不会出错??


strerror()函数根据给定的错误码返回相应的错误信息,而errno变量是一个线程局部存储的整数,用于保存在程序执行过程中发生的最后一个错误码。因此,如果同个线程同时发生了两个不同的错误,并将它们分别存入errno变量中,后续调用strerror()时只能获取到最后一次发生错误的信息。


例如,假设在程序中先调用了open()函数打开一个不存在的文件,这将导致errno被设置为ENOENT(表示没有这样的文件或目录)。然后,在接下来的代码中又调用了一些可能会改变errno值得函数而发生了另一种类型的错误。当我们使用strerror(errno)时,只能返回最后一次错误所对应的错误信息。


因此,如果想要获取每个操作过程中发生的所有错误信息,我们需要同时记录所有可能引起错误的函数调用,并对每个错误单独调用strerror()函数进行处理。


二、malloc函数


在C语言中,malloc()函数是动态分配内存的方法之一。其原型为

void *malloc(size_t size);


其中,size参数表示需要分配的字节数。


malloc()函数在内存中分配了指定大小的连续空间。

● 成功:返回一个指向该空间起始地址的指针

●失败:返回NULL


分配的内存大小可以在运行时进行调整,而不必在编译时确定。


例如,下面的代码在堆上动态地分配一个包含5个整数的数组:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *ptr = NULL;//定义一个指针
    int n, i;//定义两个整数
    printf("Enter number of elements: ");
    scanf("%d", &n);//用户输入
  //分配内存空间,sizeof(int)计算int类型的空间大小,等效于4*n
  //n输入5,就等于开辟了5个int的内存空间,可以输入5个int数进去
    ptr = (int*) malloc(n * sizeof(int)); 
    if (ptr == NULL) // 检查是否分配成功
    {
        printf("Memory allocation failed!");
        return 1;
    }
    for (i = 0; i < n; i++)
    {
        printf("Enter element %d: ", i+1);
        scanf("%d", &ptr[i]);
    }
    printf("The array elements are: ");
    for (i = 0; i < n; i++)
        printf("%d ", ptr[i]);
    free(ptr); // 释放内存空间
    return 0;
}


在上述代码中,我们首先使用malloc()函数分配了一个包含n个整数的数组所需的内存空间。然后,在用户输入每个数组元素的值后,我们打印出了数组中的所有元素。


最后,我们使用free()函数释放了该内存空间。注意到,一旦不再需要使用该内存空间,就应该及时释放它,以避免内存泄漏问题。



在代码中,可能有人搞不清楚&ptr[i]*ptr[i]的区别?

现在解释下。


在C和C++中,指针和数组的概念非常相似,因为数组名本质上也是一个指向数组首元素地址的常量指针。


因此,指针和数组可以互相转化,并且可以使用类似于数组下标的方式访问指针所指向的内存中的数据。


现在来看,&ptr[i] *ptr[i] 的区别:


&ptr[i] 表示取出 ptr 数组中第 i 个元素的地址(即 &(*(ptr+i)))。这里 * 表示对 ptr[i] 取值操作,& 表示对取值结果进行取址操作。


*ptr[i] 表示取出 ptr 数组中第 i 个元素所指向的内存中存储的数据。这里 [ ] 是一个下标操作符,表示从数组中取出第 i 个元素,* 表示访问该元素所指向的内存中存储的数据。


因此,&ptr[i] *ptr[i] 的含义是不同的。


前者返回一个地址,后者返回一个值。


同时需要注意到,& * 是互逆操作符(如 *&a 等价于 a),但它们不能替换彼此使用。



而我们在文中的这局句代码则是什么意思呢?


scanf(“%d”, &ptr[i]);

我们这里假设"%d"为第一个参数,&ptr[i]为第二个参数。

输入(scanf):先看第一个参数类型,输入第一个的参数类型给第二个参数。

输出(print):先看第二个的参数是什么类型的,根据第二个参数类型决定第一个参数。

所以说,这段代码得先看第一个参数,作用是让用户从键盘输入一个整数,然后把这个整数存到数组的某个位置上。

所谓数组,就是一堆连续的内存空间,每个内存空间都有它的编号,我们把这些编号称为“下标”。

ptr[i]就表示数组中第i个下标对应的那个内存空间里存储的值。

&ptr[i]表示那个内存空间的地址,%d表示要读入一个整数,并将它存储到这个地址上。


目录
相关文章
|
22天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别
pymalloc 和系统的 malloc 有什么区别
|
13天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
28 6
|
18天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别?
pymalloc 和系统的 malloc 有什么区别?
|
27天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
1月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
61 7
|
1月前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
30 4
|
30天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
25 0
|
30天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
21 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
1月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
39 10