strtok与strtok_r函数及线程安全问题

news2024/11/12 6:00:11

       #include <string.h>

       char *strtok(char *str, const char *delim);

       char *strtok_r(char *str, const char *delim, char **saveptr);

 总的:这两个函数都是分割字符串的函数,但是前者是线程不安全的,后者是线程安全的。

我们先从使用和学习的角度看一个例题:从键盘读入一个字符串,输出其中单词的个数。

第一次写代码如下:
 

#include<iostream>
#include<cstring>
int main() {
	char ch;
	for (;;) {
		int numOfWord = 0;
		while ((ch = getchar()) != '\n') {
			if (ch == ' ') {
				numOfWord++;
			}
		}
		std::cout << numOfWord + 1 << std::endl;
	}//这段代码有bug,因为如果输入有多个连续空格的话,numOfWord仍然会自增,导致计数不准;
	return 0;
}

测试结果如下:

正如代码中我注释的那样,如果有多个空格,那么代码就无法正确统计单词个数了。

但这难不倒我们:
 

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 128
int main() {
    char buff[N];
    while (cin.getline(buff, N)) {//从键盘读入字符串,但是'\n'它不会读入。我们打算使用'\0'作为判断字符串结束的标志;
        int pos = 0, numOfWord = 0;
        bool inWord = false;
        while (buff[pos] != '\0') { // 使用 '\0' 判断字符串结束
            if (buff[pos] != ' ') {
                if (!inWord) {
                    numOfWord++;  // 开始一个新单词
                    inWord = true;
                }
            }
            else {
                inWord = false;  // 结束当前单词
            }
            pos++;
        }
        cout << numOfWord << endl;
        memset(buff, 0, sizeof(buff));
    }
}

结果如下:

 

这下空格可以被正确地忽略了!但是这段代码显然不太容易一次性写出,有没有更加容易的办法?

当然是:strtok

#define _CRT_SECURE_NO_WARNINGS -1

#include<cstring>
#include<stdio.h>
#include<iostream>

int main() {
	const char s[2] = " ";
	char buffer[128];
	//std::cin >> buffer;
	gets_s(buffer);
	int numOfWord = 0;
	char* p = strtok(buffer, s);
	while (p != NULL) {
		printf("第%d个单词是%s\n", numOfWord + 1, p);
		numOfWord++;
		p = strtok(NULL, s);
	}
	std::cout << "总的单词个数为:" << numOfWord << std::endl;
	return 0;
}

ps:不要使用cin读取,它会跳过空格; 

这就是strtok的一个用法;但是在学习LINUX系统编程的时候,我发现它是线程不安全的,多个线程在执行相同的代码,但结果是不同的;

举例:
 

#include<iostream>
#include<unistd.h>
#include<cstdio>

#include<semaphore.h>
#include<pthread.h>
#include<cstdlib>
#include<cstring>

using namespace std;


void *fun(void *arg)
{
  char buff[128] = {"a b c d e f"};
  char *s = strtok(buff, " ");
  while (s != NULL)
  {
    printf("fun s=%s\n", s);
    sleep(1);
    s = strtok(NULL, " ");
  }
}
int main()
{
  pthread_t id;
  pthread_create(&id, NULL, fun, NULL);

  char buff[128] = {"1 2 3 4 5 6"};
  char *s = strtok(buff, " ");
  while (s != NULL)
  {
    printf("main s=%s\n", s);
    sleep(1);
    s = strtok(NULL, " ");
  }

  exit(0);
}

上述代码运行结果可能为:

也可能为:
 

其实还有其他情况,但这两张图片已经够了:同样的多线程代码运行了两次出现了不同情况 。究其原因其实是strtok函数搞的鬼,它维护一个全局可见的静态变量:

The point where the last token was found is kept internally by the function to be used on the next call (particular library implementations are not required to avoid data races).


对于我们的代码而言,我们有main函数作为主线程,fun函数作为子线程,两者同时运行,但是strtok函数的静态变量全局只有一份,那么一个就会覆盖另一个的值,main函数如果先执行,切割了一个1,此时如果fun再执行,char *s = strtok(buff, " ");这一行代码会导致重新分割,重新来过。下一次如果是main函数的执行就到了while循环中的    s = strtok(NULL, " ");它会以空格为分界点,继续切割,没想到它被静态变量欺骗了,静态变量存储的是fun函数中(而不是main函数)标记的位置,所以main打印了b。以此类推,一直到f。特别地,如我们第二张图片所示,两个线程同时访问到了那个静态变量,都打印了f。

所以我们应该怎么做?我们可以使用strtok_r函数:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

void *fun(void *arg)
{
  char buff[128] = {"a b c d e f"};
  char *ptr = NULL;
  char *s = strtok_r(buff, " ", &ptr);
  while (s != NULL)
  {
    printf("fun s=%s\n", s);
    sleep(1);
    s = strtok_r(NULL, " ", &ptr);
  }
}
int main()
{
  pthread_t id;
  pthread_create(&id, NULL, fun, NULL);

  char buff[128] = {"1 2 3 4 5 6"};
  char *ptr = NULL;
  char *s = strtok_r(buff, " ", &ptr);
  while (s != NULL)
  {
    printf("main s=%s\n", s);
    sleep(1);
    s = strtok_r(NULL, " ", &ptr);
  }

  exit(0);
}

无论如何执行,结果如下:
 

它是线程安全的,它的第三个参数是一个二级指针,即:各自使用局部变量来记录自己已经切割到了哪里。

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

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

相关文章

网络药理学:分子对接之二:PDB数据库的使用(已知PDB ID)、PubChem数据库如果没有3D结构

PDB数据库使用 官方地址&#xff1a;https://www.rcsb.org/ 首页如下&#xff1a; 我们以热休克蛋白HSP90AA1为例&#xff0c;其PDB ID为7DHG&#xff0c;所以我们在搜索栏输入7DHG&#xff1a; 主要关注红框里的几个地方。 Download 下载文件&#xff0c;一般选择PDB For…

车载以太网

目录 概述 发展历史 总体架构 相关组织介绍 主流车载网络系统 各种总线比较 概述 随着汽车电动化进程的加速推进,手机控制车辆以及彼此交互的场景不断扩大,可以想象未来联网需求只会不断拓展,无论是车内还是车外的联网需求都不约而同的提出了更多网络带宽的重要性。 为…

知识赋能:构建高效测试团队的关键

目录 ​​​​​​问题背景 知识库的重要性 新员工的融入与关键岗位的风险控制 知识库的构成 常见问题讨论 团队历史包袱重、老员工不配合&#xff0c;怎么办&#xff1f; 1. 明确愿景和目标 2. 激励与认可 3. 赋予责任与参与感 4. 循序渐进&#xff0c;逐步推进 5.…

C# 路径操作

一、打开程序所在路径 try{string debugPath System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);System.Diagnostics.Process.Start(debugPath);}catch (Exception ex){MessageBox.Show("无法打开目录&#xff1a;" e…

[开源]YOLOv8+Pyside6的交通红绿灯目标检测源码

[开源]YOLOv8Pyside6的交通红绿灯目标检测源码 一. 项目介绍源码链接 该系统是yolov8目标检测可视化界面检测系统&#xff0c;支持图片、视频、摄像头检测. 系统的模型是自己训练的模型, 源码自取 源码链接 如需自己训练模型, 数据集链接 二. 作者的运行环境 python3.8tor…

一文精通Fourier Transform--傅里叶变换

导读&#xff1a; 在数字信号处理中&#xff0c;我们把信号分为时域信号与频域信号。傅里叶发现&#xff1a;任何周期信号(时域)都能够由不同频率谐波的正弦波(频域)叠加而成。沟通起时域频域最为关键的一点就是我们要学习的傅里叶变换&#xff08;Fourier Transform&#xff0…

党务政务服务|基于SprinBoot+vue的党务政务服务热线系统(源码+数据库+文档)

党务政务服务热线系统 目录 基于SprinBootvue的党务政务服务热线系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 管理员功能模块 管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…

威胁建模STRIDE框架

威胁建模STRIDE框架 1.威胁建模框架概述2.STRIDE威胁建模框架欺骗 - 冒充某人或某物篡改 - 未经授权更改数据否认性 - 不宣称对执行的操作负责信息泄露 - 查看不应查看的数据拒绝服务 - 系统繁忙权限提升 - 拥有本不应该拥有的权限 3.后续的威胁发现 1.威胁建模框架概述 威胁建…

如何通过内网穿透实现Pycharm远程服务器编译项目与服务器代码同步

文章目录 前言一、前期准备1. 检查IDE版本是否支持2. 服务器需要开通SSH服务 二、Pycharm本地链接服务器测试1. 配置服务器python解释器 三、使用内网穿透实现异地链接服务器开发1. 服务器安装Cpolar2. 创建远程连接公网地址 四、使用固定TCP地址远程开发 前言 本文主要介绍如…

Flex布局最后一行元素的对齐的解决方案

问题的产生 使用Flex布局&#xff0c;设置justify-content: space-between;让元素在主轴上两队对齐。 <div class"box"><div class"item">1</div><div class"item">2</div><div class"item">3&l…

考试:软件工程(01)

软件开发生命周期 ◆软件定义时期&#xff1a;包括可行性研究和详细需求分析过程&#xff0c;任务是确定软件开发工程必须完成的总目标&#xff0c; 具体可分成问题定义、可行性研究、需求分析等。 ◆软件开发时期&#xff1a;就是软件的设计与实现&#xff0c;可分成概要设计…

【PPT】文字突然变成方框

文章目录 前言一、问题描述二、解决方案参考文献 前言 在 ppt 画图时遇到的问题 一、问题描述 在 ppt 使用过程中&#xff0c;同一字体&#xff0c;有些变成了方框&#xff0c;有些没有变&#xff08;排除字体缺失问题&#xff09; 二、解决方案 如果是页数多了&#xff0…

【用map解决高频单词问题】返回前k个高频单词

目录 1.前言2.题目简介3.求解思路4.示例代码4.1换个稳定排序解决4.2用仿函数强行进行控制 1.前言 ok&#xff0c;好久不写博客了&#xff0c;这里简单的来写一写用到关于容器map来解决前k个高频单词的问题。 当然&#xff0c;这个问题也可以用优先级队列(堆)来解决&#xff0c…

免费扫描试卷的软件有哪些?5个软件帮助你轻松进行试卷扫描

免费扫描试卷的软件有哪些&#xff1f;5个软件帮助你轻松进行试卷扫描 扫描试卷的软件可以帮助你将纸质试卷转化为电子版&#xff0c;方便保存、分享和编辑。以下是五款免费的试卷扫描软件&#xff0c;它们功能强大且易于使用&#xff0c;能够帮助你轻松完成试卷扫描。 试卷…

【精彩瞬间】2024外滩大会回顾

9月8号&#xff0c;为期3天的“2024 inclusion外滩大会”在上海黄浦圆满落下帷幕。本届大会&#xff0c;共吸引了5.2万人到场参观&#xff0c;无论是参会规模还是国际嘉宾的数量都创下历史新高。 500位演讲嘉宾分别在1场开幕主论坛、36场见解分论坛上聚焦“ai产业新实践”“科技…

基于STM32的保温水壶控制器设计

文章目录 前言资料获取设计介绍功能介绍设计源码具体实现截图参考文献设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设…

【MATLAB】矩阵的合并

矩阵的合并是指将两个或者多个矩阵合并到一起构成一个新的矩阵。矩阵标识符方括号 [ ]&#xff0c;不仅可以用来创建新的矩阵&#xff0c;还可以用来将若干个矩阵合并到一起。表达式 C [A B] 将矩阵A和B在水平方向上合并到一起&#xff0c;而表达式C[A;B]则将矩阵A和B在竖直方…

java反射(reflection)的基本理解和使用

目录 一、什么是反射 二、反射的主要用途&#xff1f; 三、什么情况下使用反射 四、反射有什么优点&#xff1f; 1、增加程序的灵活性 2、避免将固有的逻辑程序写死到代码里 3、提高代码的复用率 4、支持动态代理和动态配置 5、支持自动化测试和代码生成 6、自由度高…

ABAP EXCEL宏函数应用

【应用场景】 1. excel导出项目及对应的分期,楼栋的各个产品类型对应的各个面积指标数据, 分项目/分期/楼栋三个SHEET页签。当用户在楼栋层级编辑完产品类型对应的面积指标时,可以 通过宏函数自下往上先汇总到相同产品类型+面积指标的分期层级,再自动汇总到项目层级面积…

万龙觉醒免费辅助脚本:VMOS云手机助力物资获取与养成!

在《万龙觉醒》中&#xff0c;资源获取和英雄养成是游戏的重要组成部分&#xff0c;而使用VMOS云手机可以为玩家带来更高效的游戏体验。通过使用VMOS云手机&#xff0c;玩家可以轻松实现24小时不间断的游戏辅助&#xff0c;无需反复安装或更新游戏&#xff0c;因为VMOS云手机自…