前言
我的第一门语言就是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表示要读入一个整数,并将它存储到这个地址上。