GCC下“initializing ‘char **’ with an expression of type ‘const char **’ discards qualifiers in nested pointer types”警告解读

我曾经被这个警告困扰了好久好久,问了好些人,都说没遇到过=_=…
问题代码片段(与问题无关的代码块已被忽略):

size_t MyConv(const char *orginStr, const char *targetStr, ...) {
    char **tmpIn = NULL;
    char **tmpOut = NULL;
        ...
        ...
    tmpIn = &orginStr;
    tmpOut = &targetStr;
        ...
        ...
}

GCC编译后会弹出如下警报:

sfind.c:887:8: warning: assigning to 'char **' from 'const char **' discards qualifiers in nested pointer types
      [-Wincompatible-pointer-types-discards-qualifiers]
        tmpIn = &orginStr;
              ^ ~~~~~~~~~

这条警告信息翻译出来是“用类型表达式初始化“char \**” “const char \**”丢弃嵌套指针类型中的限定符。 ”,个人表示很晦涩难懂。。。
后来翻阅《C专家编程》的时候,偶然间发现一个章节是讨论const限定符的,认真看了下,顿时感慨自己学识太过浅薄。
上面的代码中,实参const char \*orginStr的const是修饰char orginStr的,而char **tmpOut是指向char *类型变量的一个指针(此“char *”类型无修饰符),这两点倒是没有疑惑。但当二者互相赋值的时候,const char *orginStrchar \**tmpOut这两个指针所指向的类型的修饰符不同。一个是const char orginStr,带了const修饰符,而另一个则是char *tmpOut,并没有const修饰符。指针变量赋值时,赋值语句左边的指针所指向的类型必须具有赋值语句右边的类型所拥有的全部修饰符,才能正常通过编译,否则就会舍弃右边变量多余的修饰符。

以下内容摘自ANSI C标准手册

两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。

也就是说:

int *a = NULL;
const int *b = a;

上面这段代码是可以正常通过编译的,因为赋值语句左边的指针指向的数据类型拥有了右边的数据类型的所有修饰符(甚至还比右边的多了一个const)而下面这段却不行。

const int *a = NULL;
int b = a;

在Linux下利用宏定义的方式使用Sleep函数

前言

Sleep是存在于Windows.h头文件中用于将进程挂起的函数。显然,Linux下是没有该头文件的,该如何实现呢?

实现方法

Linux的unistd.h库为我们提供了类似功能的usleep函数,但该函数的计时单位是微妙而不是和Sleep一样使用毫秒计时。
为了方便程序的跨平台开发,可以使用如下宏定义将usleep化妆为Sleep

#ifdef linux
#include<unistd.h>
#define Sleep(value) usleep(value * 1000);
#endif

只需将以上宏定义加到源码中就可以正常的在Linux下使用Sleep函数了。

GNU出品的转码库——libiconv v1.15 for windows x64下载(已经过编译)

libiconv库是GNU项目的一部分,负责对字符进行不同编码间的转换。是Linux下非常常用的编码转换库,其对应的命令是:iconv(命令的使用方法可以百度了解下)。

libiconv库原生支持Windows平台,但考虑到很多人为了追求效率而不想自己动手编译这个庞大而复杂的库,故我在这里为大家提供已经编译好的支持windows 64位的二进制文件——可以动态调用也可以静态编入程序,提供Debug和Release两个版本。
注:使用本库请确保你的项目是64位的,否则无法正常调用。

二进制文件已在Win10 + VS 2017环境中测试通过。提供的版本为libiconv v1.15,该版本发布于2017年2月,截至本文章发布时为libiconv库的最新版本。

下载地址:
libiconv-1.15 for windows x64

C语言swprintf函数的正确用法

在这篇文章的开头,我要狠狠的吐槽国内那些专门抄别人文章的网站主们,抄是可以抄,但是一篇错的文章还抄来抄去的有意思吗???

swprintf函数原型:

int swprintf(wchar_t * ws,size_t len,const wchar_t * format,...);

参数说明:

ws

指向存储宽字符串的缓冲区的指针。
缓冲区的大小至少为n个宽字符。

len

填充ws缓冲区的最大宽字符数。
生成的字符串长度至多为n-1,为额外的’0’留一个空间。

format

宽字符串,其中包含格式字符串,其格式与printf中的格式相同。
请注意,所有格式说明符的含义与printf中的相同; 因此,应使用%lc写宽字符(而不是%c),并且%ls应用于宽字符串(而不是%s)。

… (附加参数)

同printf的附加参数,不做过多解释。

程序示例:

#include <stdio.h>
#include <wchar.h>
int main (void) {
    wchar_t str [100] = L"";
    swprintf (str, 100, L"%ls%d is %d",L"The half of " ,80, 80/2);
    fputws (str, stdout);
    return 0;
}

C语言操作文件时写完一组数据后立即刷新缓存区的方法

前言

很多时候,为了防止程序意外结束等原因造成的数据丢失,我们需要在向文件写入一组数据以后立即刷新缓存区。比如说:在写程序LOG的时候,为了防止程序崩溃时无法记录LOG,就需要记录一条LOG就刷新一次缓存区以使其立即写入磁盘文件。

实现方法

使用C标准库提供的fflush(FILE *)函数可以很方便的实现该功能,只需传入要刷新缓存区的文件指针即可。

示例程序

#include <stdio.h>
int main(void) {
    FILE *fp = fopen("LOG.txt", "w");
    fprintf(fp, "hello!");// 使用一个明显小于缓存区的字符串做测试,若大于缓存区则缓存区在满了以后会自动刷新
    fflush(fp);           // 立即刷新缓存区,若删除此语句则字符串不会被保存在文件中。
    fp++;                 // 偏移文件指针到未知地址。
    fclose(fp);           // 由于fp指向未知地址,所以执行fclose后程序会崩溃。
    return 0;
}

经观察可以发现,不使用fflush()函数,字符串就不能被正确的保存在文件中。

原来C语言的数组大小可以是一个变量呀!

长久以来,我一直以为C语言的数组大小必须是在编译前就确定下来的常量值,相信很多C语言初学者也是和我一样的认识。然而,直到今天我才发现原来从C99标准开始C语言就已经支持了可变长数组

例如如下程序(注:VS系列编译器均不支持该特性,搞不懂微软为何不好好支持C99标准):

#include <stdio.h>
int main(void) {
    int a = 2;
    int b[a + 1];   // 数组b的长度只有在运行时才可以确定。
    for (int i = 0; i < 3; i++) {
        b[i] = i;
        printf("%d\n", b[i]);
    }
    return 0;
}

上面的代码在GCC 7.3上测试通过(实际上任何支持C99标准的编译器都可以)。
需要注意的是,在C语言中,可变长数组只能用于局部变量中,在全局变量中使用可变长数组会导致编译器报错。
另外,可变长数组不等于动态数组,也就是说,数组的维度在变量的整个生命周期中是不可变的。
可变长数组对于二维、三维…的数组及类似int (*a)[b/*b是个变量*/ + 2];这种指向二维数组的指针也一样适用。

C语言实现Linux下遍历指定目录批量搜索及替换文件中关键字的小工具

为什么要写这样一个程序呢?

兼职做IDC的我经常会需要帮客户改网站,很多时候会需要将用户网站中所有脚本文件中的某一关键字进行替换(可能是品牌名称、网址等),在Windwos下一般都是用Dreamweaver来实现的,但总有一种杀鸡用牛刀的感觉。后来电脑重装为Linux平台后一直没发现好的批量替换工具,这导致很长一段时间以来我一直是用grep命令先搜索关键字,再挨个文件手工替换=_=。。。
寒假前听说高考科那边要开C语言的课程了,索性就跟着学啦,因为我初中的时候有看过一点点C++的知识,所以C学起来也还算轻松,到放寒假时已经学了两个月了,感觉是时候利用假期来写点什么了。想到前面说的痛点,我就决定写一个小巧的工具,可以方便的批量替换目录中所有文件中包含的关键字。

程序简介

首先声明,这是我写的第一个程序(打印hello, world那种“程序”不算哈),水平有限,若是代码中什么可笑的地方还望大佬们可以给指出来^_^。
程序是用C语言+Shell脚本实现的,目前仅支持Linux平台,计划支持Windows(工作量不会很大,核心代码通用)。
程序中关键字的数据存储是使用链表这种数据结构实现的,没学过链表的同学看起来可能比较吃力。

若是程序中有什么BUG或是想和我交流一下可以在文章下方留言。

程序源码:

GitHub:https://github.com/sunxiyuan/sfind

感觉还可以的话给个Star呗~

C语言实现删除一个字符串中的指定字母

题目要求

删除一个字符串中的指定字母,如:字符串 “hello,world!”,删除其中的 o 字母,应输出 “hell,wrld!”

解题思路

将字符串内不属于需搜索的字符的字符再赋值到该字符串的地址空间中,最后再在字符串结尾加上结束标记\0

程序代码

#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
    int num = 0;
    for (int i = 0; i <= strlen(argv[1]); i++)
        if (argv[1][i] != argv[2][0])
        {
            argv[1][num] = (i == strlen(argv[1])) ? '\0' : argv[1][i];
            num++;
        }
    printf("%s\n", argv[1]);
    return 0;
}

运行结果

2018-02-22 08-22-51屏幕截图.png

C语言依据用户输入的数据大小动态开辟内存空间

前言

使用C语言编程时经常需要从各种途径读取数据并将其保存在内存中,但C语言为了保持简洁,所以只包含一些很基础的数据类型,有时候并不能很好的存储用户输入的数据。
例如:需要存储用户输入的字符串,通常,教科书上所教的方法是分配一个较大的数组来进行存储。显然,这在实际生产环境中是相当不切实际的。
为了相对完美的解决这个问题,我们可以通过动态的开辟堆空间来实现对数据的存储。

例程简介

此例程每次接收一个字符,并利用realloc函数根据数据量大小来重新分配堆空间。

例程代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

int main(void) {
    char tmp = '\0';
    char *str = NULL;
    
    for (int i = 0; true; i++) {
        tmp = getchar();
        
        if ((tmp == '\r') || (tmp == '\n'))
            break; /*每次循环都按照当前数据量大小来重新分配堆空间大小*/
            
        str = (char *) realloc(str, sizeof(str) + sizeof(tmp));
        
        if (str == NULL)
            return -1;
        
        str[i] = tmp;
    }
    
    printf("%s\n", str);
    
    return 0;
}

执行结果

输入:hello,it7e!!!,程序动态开辟内存存储字符串并输出hello,it7e!!!,如下图。
2018-02-10 17-53-23屏幕截图.png

解决Ubuntu下无法使用pthread库的问题(加-lpthread也不好使的情况)

导语

这个问题困扰了我很久,因为写一个飞机大战的小游戏需要用到多线程。看到Linux下的多线程可以用pthread.h库来实现,但该库不是C的标准库,所以编译的时候要加上-lpthread,可是,为在我的Ubuntu 16.04下如此编译的结果是报了这个错误:

/usr/bin/ld: 找不到 -lphtread
collect2: error: ld returned 1 exit status

为了解决这个问题,我在网上苦苦地搜了很久很久,最后无奈地发现:网上现有的文章基本上都是抄来抄去,全是胡言乱语。。。

正确的解决办法

安装manpages-posix软件包即可。

$ sudo apt install manpages-posix

再次编译即可通过!

我对C语言中字符串指针的一个误解

在QQ群中看到一道C语言题:

定义char a[10]和char *p=a,下面的赋值语句中,正确的是___
A. a[10]=”Turbo C” B. p=”Turbo C”
C. a=”Turbo C” D. *p=”Turbo C”

这道题选B本身是没有任何值得疑惑的地方的,但是,且看我下面这个小程序。

#include<stdio.h>
int main(void) {
    char a[10];
    char *p = a;
    p = "Turbo C";
    printf("%s\n", a); //这里我打印的是数组a而不是指针p
    return 0;
}

在Linux下编译执行后输出乱码,这个错误是因为我一开始以为:p = “Turbo C”是将”Turbo C”这个字符串赋值给了p所指向的字符数组a。
实际上,p = “Turbo C”这条语句在执行时做了三件事。

  1. 在内存中创建了一个常量空间,用于存放字符串
  2. 在字符串末尾添加了一个/0
  3. 返回字符串在内存中存放位置的首地址给p指针

所以,p指针中所保存的内容已经由字符数组a的首地址变为了字符串”Turbo C”的首地址,想当然的,字符串”Turbo C”也并未被赋值给字符数组a,而是存在于新的内存空间中。
此时我再用printf输出字符数组a的内容就会打印出一些乱七八糟的东西啦——因为我没有给数组a初始化,只有天才晓得编译器在里面填了什么东东~

那么上面那道程序这样改一下就正常工作啦:
将第7行改为

printf("%s\n", p);

C11新增多线程支持库-threads.h参考手册

线程管理

int thrd_create(thrd_t *thr,  thrd_start_t func,  void *arg);

thrd_create创建一个新线程,该线程的工作就是执行func(arg)调用,程序员需要为线程编写一个函数,函数签名为:thrd_start_t ,即int (*)(void*)类型的函数。新创建的线程的标识符存放在thr内。

thrd_t thrd_current(void);

thrd_current函数返回调用线程的标识符。

int thrd_detach(thrd_t thr);

thrd_detach会通知操作系统,当该线程结束时由操作系统负责回收该线程所占用的资源。

int thrd_equal(thrd_t thr0, thrd_t thr1);

thrd_equal用于判断两个线程标识符是否相等(即标识同一线程),thrd_t是标准约定的类型,可能是一个基础类型,也可能会是结构体,开发人员应该使用thrd_equal来判断两者是否相等,不能直接使用==。即便==在某个平台下表现出来是正确的,但它不是标准的做法,也不可跨平台。

void thrd_exit(int res)

thrd_exit函数提早结束当前线程,res为它的退出状态码。这与进程中的exit函数类似。

int thrd_join(thrd_t thr, int *res)

thrd_join将阻塞当前线程,直到线程thr结束时才返回。如果res非空,那么res将保存thr线程的结束状态码。如果某一线程内没有调用thrd_detach函数将自己设置为detach状态,那么当它结束时必须由另外一个线程调用thrd_join函数将它留下的僵死状态变为结束,并回收它所占用的系统资源。

void thrd_sleep(const xtime *xt)

thrd_sleep函数让当前线程中途休眠,直到由xt指定的时间过去后才醒过来。

void thrd_yield(void)

thrd_yield函数让出CPU给其它线程或进程。

互斥对象和函数

threads.h中提供了丰富的互斥对象,用户只需在mtx_init初始化时,指定该互斥对象的类型即可。

int mtx_int(mtx_t  *mtx, int type);

mtx_init函数用于初始化互斥对象,type决定互斥对象的类型,一共有下面6种类型:

  • mtx_plain –简单的,非递归互斥对象
  • mtx_timed –非递归的,支持超时的互斥对象
  • mtx_try –非递归的,支持锁检测的互斥对象
  • mtx_plain | mtx_recursive –简单的,递归互斥对象
  • mtx_timed | mtx_recursive –支持超时的递归互斥对象
  • mtx_try | mtx_recursive –支持锁检测的递归互斥对象
int mtx_lock(mtx_t *mtx)
int mtx_timedlock(mtx_t *mtx, const xtime *xt)
int mtx_trylock(mtx_t *mtx)

mtx_xxxlock函数对mtx互斥对象进行加锁 , 它们会阻塞,直到获取锁,或者xt指定的时间已过去。而trylock版本会进行锁检测,如果该锁已被其它线程占用,那么它马上返回thrd_busy

int mtx_unlock(mtx_t *mtx)

mtx_unlock对互斥对象mtx进行解锁。

条件变量

threads.h通过mtx对象和条件变量来实现wait-notify机制。

int cnd_init(cnd_t *cond)

初始化条件变量,所有条件变量必须初始化后才能使用。

int cnd_wait(cnd_t *cond, mtx_t *mtx)
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const xtime *xt)

cnd_wait函数自动对mtx互斥对象进行解锁操作,然后阻塞,直到条件变量condcnd_signalcnd_broadcast调用唤醒,当前线程变为非阻塞时,它将在返回之前锁住mtx互斥对象。cnd_timedwait函数与cnd_wait类似,例外之处是当前线程在xt时间点上还未能被唤醒时,它将返回,此时返回值为thrd_timeoutcnd_waitcnd_timedwait函数在被调用前,当前线程必须锁住mtx互斥对象。

int cnd_signal(cnd_t *cond)
int cnd_broadcast(cnd_t *cond)

cnd_broadcast唤醒那些当前已经阻塞在cond条件变量上的所有线程,而cnd_signal只唤醒其中之一。

void cnd_destroy(cnd_t *cond)

销毁条件变量。

初始化函数

试想一下,如何在一个多线程同时执行的环境下来初始化一个变量,即著名的延迟初始化单例模式。你可能会使用DCL技术。但在C11中,你可以直接使用call_once函来实现。

void call_once(once_flag *flag, void (*func)(void))

call_once函数使用flag来保确func只被调用一次。第一个线程使用flag去调用call_once时,函数func会被调用,而接下来的使用相同flag来调用的call_oncefunc均不会再次被调用,以保正func在多线程环境只被调用一次。

线程专有数据(TSD) 和线程局部数据 (TLS)

在多线程开发中,并不是所有的同步都需要加锁的,有时巧妙的数据分解也可减少锁的碰撞。每个线程都拥有自己私有数据,使用它可以减少线程间共享数据之间的同步开销。

如果要将一些遗留代码进行线程化,很多函数都使用了全局变量,而在多线程环下,最好的方法可能是将这些全局量变量换成线程私有的全局变量即可。

TSDTLS就是专门用来处理线程私有数据的。 它的生存周期是整个线程的生存周期,但它在每个线程都有一份拷贝,每个线程只能read-write-update属于自己的那份。如果通过指针方式来read-write-update其它线程的备份,它的行为是未定义的。

TSD可认为线程私有内存下的void *组数,每个数据项的key对应于数组的下标,用于索引功能。当一个新线程创建时,线程的TSD区域将所有key关联的值设置为NULLTSD是通过函数的方式来操作的。C11TSD提供的标准函数如下:

int tss_create(tss_t *key,  tss_dtor_t dtor)
void tss_delete(tss_t key)
void *tss_get(tss_t key)
int tss_set(tss_t key, void *val)

tss_create函数创建一个keydtor为该key将要关联value的析构函数。当线程退出时,会调用dtor函数来释放该key关联的value所占用的资源,当然,如果退出时value值为NULLdtor将不被调用。tss_delete函数删除一个keytss_get/tss_set分别获得或设置该key所关联的value

通过上述TSD来操作线程私有变量的方式,显得相对繁琐; C11提供了TLS方法,可以像一般变量的方式去访问线程私有变量。做法很简单,在声明和定义线程私变量时指定_Thread_local存储修饰符即可,关于_Thread_local,C11 有如下的描述:

  1. 在声明式中,_Thread_local只能单独使用,或者跟staticextern一起使用。
  2. 在某一区快中声明某一对象,如果声明存储修饰符有_Thread_local,那么必须同时有staticextern
  3. 如果_Thread_local出现在一对象的某个声明式中,那么此对象的其余各处声明式都应该有_Thread_local存储修饰符。
  4. 如果某一对象的声明式中出现_Thread_local存储修饰符,那么它有线程储存期。该对象的生命周期为线程的整个执行周期,它在线程出生时创建,并在线程启动时初始化。每个线程均有一份该对象,使用声明时的名字即可引用正在执行当前表达式的线程所关联的那个对象。

TLS方式与传统的全局变量或static变量的使用方式完全一致,不同的是,TLS变量在不同的线程上均有各自的一份。线程访问TLS时不会产生data race,因为不需要任何加锁机制。TLS方式需要编译器的支持,对于任何_Thread_local变量,编译器要将之编译并生成放到各个线程的private memory区域,并且访问这些变量时,都要获得当前线程的信息,从而访问正确的物理对象,当然这一切都是在链接过程早已安排好的。

以下列出本文参考的资料,在此向原作者致敬。

使用C11新增的多线程支持库-threads.h进行多线程编程

2019年12月6日更新

首先要感谢评论区的热心的同学们的提醒,经本人亲自验证:

VS 2019中移除了对threads.h的支持(评论区有同学提到单独安装v140工具集可以实现支持,但是测试发现没有效果),而GCC方面则在最新版中加入了对该头文件的支持。

以下为原文(程序代码部分更新了Linux版Dome)。


导语

threads.h是C11标准新增的多线程支持库,在此之前C语言实现多线程,除了使用系统API外用的最多的就是pthread.h了,threads.h在语法上和pthread.h非常相似。
当然,对于新出的C语言标准,各大编译器厂商并不会马上就支持。就比如说,Linux下主流的C语言编译器————GCC,直到GCC7.2版本都没能支持该库(PS:看到老外网站上说要安装最新版glibc才能获得对该库的支持,然而亲测无卵用)。相反,VS在这方面做的就很不错,VS2017已经可以完美支持该库了,本文也将基于VS2017社区版对该库的使用方法做介绍。

注:本文仅对多线程编程的概念及threads.h库文件的使用方法做简单介绍,并不会详尽介绍该库下的所有函数,如果你需要一个函数功能的参考手册可以参考此篇文章:C11新增多线程支持库-threads.h参考手册

以一个小程序为例子

本程序中使用到的库函数及宏:

  • thrd_t //此宏定义用于存放线程标识符的数据类型
  • thrd_create //此函数用于创建线程
  • thrd_detach //此函数用于通知操作系统,当线程结束时由操作系统负责释放资源
  • thrd_exit //此函数用于结束当前线程

程序功能:

主线程每2秒打印一次“I love ibadboy.net~~~”,共打印10次。子线程每1秒打印一次“He love ibadboy.net!!!”,共打印10次。我们知道,在只有一个主线程的C程序中该功能是无法实现的,因为后一段程序代码必须等待前一段代码执行完毕才可执行。但,在多线程编程中,各个线程可以一起执行(这里涉及到的同步、异步等等的高阶技术就不讨论了)。举个例子:在游戏开发中,如果程序需要实时监控用户键盘的输入,就不能把这段代码放到主线程中,因为这样的话该段代码就会被程序的其他部分阻塞掉而无法做到真正的“实时”,这时就可以利用多线程技术来化解尴尬啦!话不多说,直接上代码!

程序代码Linux版本

#include <stdio.h>
#include <unistd.h>	//包含sleep等函数
#include <stdbool.h>
#include <threads.h>	//包含多线程支持库头文件
#include <stdlib.h>	//包含exit等函数

int thr_fun(void *);

int main(void) {
    thrd_t thr;
    int ret; //保存thrd_create函数的返回值用于判断线程是否创建成功:0为成功,1为失败。
    ret = thrd_create(&thr, thr_fun, NULL); //将thr_fun函数放在一个新的线程中执行
    if (ret != thrd_success) {
        printf("error!!!\n");
        getchar();
        exit(-1);
    }
    ret = thrd_detach(thr); //通知操作系统,该线程结束时由操作系统负责释放资源。
    if (ret != thrd_success) {
        printf("error!!!\n");
        getchar();
        exit(-1);
    }
    for (int i = 0; i < 10; i++) {
        sleep(2);
        printf("I love ibadboy.net~~~\n");
    }
    getchar();
    return 0;
}

int thr_fun(void *argv) {
    int i = 0;
    while (true) {
        i++;
        sleep(1);
        printf("He love ibadboy.net!!!\n");
        if (i == 10) {
            thrd_exit(0);
        }
    }
}

编译命令:

gcc a.c -std=c11 -lpthread

程序代码Windows版本(经测试在VS 2019中已无法编译通过):

#include<stdio.h>
#include<stdbool.h>
#include<thr/threads.h>        //包含多线程支持库头文件
#include<Windows.h>
void thr_fun(void);
int main(void) {
    thrd_t thr;
    int ret;    //保存thrd_create函数的返回值用于判断线程是否创建成功:0为成功,1为失败。
    ret = thrd_create(&thr, thr_fun, NULL);        //将thr_fun函数放在一个新的线程中执行
    if (ret != thrd_success) {
        printf("error!!!\n");
        getchar();
        exit(-1);
    }
    ret = thrd_detach(thr);    //通知操作系统,该线程结束时由操作系统负责释放资源。
    if (ret != thrd_success) {
        printf("error!!!\n");
        getchar();
        exit(-1);
    }
    for (int i = 0; i < 10; i++) {
        Sleep(2000);
        printf("I love ibadboy.net~~~\n");
    }
    getchar();
    return 0;
}
void thr_fun(void) {
    int i = 0;
    while (true) {
        i++;
        Sleep(1000);
        printf("He love ibadboy.net!!!\n");
        if (i == 10) {
            thrd_exit(0);
        }
    }
}

程序输出:

C语言多线程演示小程序的代码输出

程序中用到的库函数介绍:

thrd_create函数用于创建新线程,如果创建成功,该函数会返回thrd_success,否则返回thrd_error

函数原型:

int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);

参数说明:

  • thr:指向放置新线程标识符的内存位置的指针。
  • func:要放在子线程中执行的函数。
  • arg:传递给执行的函数的参数,无参数填NULL。

thrd_detach函数用于通知操作系统,当线程结束后由操作系统负责释放资源,如果成功,则返回thrd_success,否则为thrd_error,如不调用该函数,则线程所使用的资源将在程序全部执行完后才会释放。

函数原型:

int thrd_detach( thrd_t thr );

参数说明:

  • thr:要作用的线程的标识符

thrd_exit函数用于结束当前进程,值得一提的是:使用该函数可以在不影响子进程的情况下结束主进程,而使用exit函数的话结束主进程将会连带结束整个程序。

函数原型:

_Noreturn void thrd_exit( int res );

参数说明:

  • res:要返回的值

结语

看完此篇文章相信你已经对使用threads.h库进行C语言的多线程开发有了初步了解,如果还有什么问题的话欢迎在评论区留言哦~