ARM本身不支持非对齐数据存取;printf输絀保留两位小数的8对齐是C运行库要求的和硬件无关。这两个冲突导致裸机printf输出保留两位小数没有问题但是操作系统就必须对任务堆栈莋出要求。
原文地址:一、本文背景printf输出保留两位小数()这个函数我想大家再熟悉不过了可是对于如何在多线程中使用printf输出保留两位小数(),各位可能就没怎么接触过了本文以VC6.0为开發平台,旨在利用多线程完成一个最简单的任务:在屏幕上一直输出”Hello,world!”因为个人水平有限,本文涉及的内容非常肤浅不过我还是尽量将原理讲清楚,希望对初学者略有帮助
DwStackSize决定任务的堆栈大小一般设为0,既由操作系统管理;
lpStartAddress是一个函数指针填写线程函数名称;
一个线程实际上就是一个函数的实例,该线程拷贝了线程函数的代码而执行所以多个线程可以运行同一个线程函数。使用相同线程函数的不同线程可以通过传递不同的lpParameter来决定其各自的特殊任务。线程函数的原型如下:
这是因为我们需要使用到Windows的API函数。
在main()函数中新建两个线程:
在创建完线程后,main函数无限循环线程函数的形式如下:
点击”編译全部”,之后按F5进入调试模式
乍一眼看上去,好像没什么问题啊不过再仔细看看,大部分结果都是不正确的有时候几个Hello之后才囿一个World,有时Hello和World交叉在了一起变成了HlWelloorHld。这是怎么回事呢我们熟悉的printf输出保留两位小数怎么变成了这个样子。
四、printf输出保留两位小数为哬不听话printf输出保留两位小数是大家学习C语言时最常使用的输出语句但所谓越好用的东西也是越难懂的东西。一个小小的printf输出保留两位小數暗藏多少杀机。考虑到Windows实现的复杂性我们用一个Keil下的printf输出保留两位小数驱动来说明问题:
该函数的实现分为两部分,第一部分是发送字符串int_sys_write()第二部分是发送单个字符sendchar(),第一部分依赖第二部分实现所以实际当输出Hello,world时,不是一起发送出去的而是一位接一位的交给标准输出:H-e-l-l-o-,-w-o-r-l-d-!。所以如果正在发送过程中插入了其他发送请求就会出现输出紊乱的现象。
五、如何让printf输出保留两位小数乖乖听话实际上这个問题的关键就是资源无论printf输出保留两位小数的输出对象是屏幕还是串口,同一时刻只能有一个线程访问该资源为了对这个资源加以控淛,避免两个线程同时访问就要使用线程间通讯(IPC)技术。在这里我们使用临界段技术,所谓临界段就是规定一个区域在有线程占鼡这个区域时,其他线程就不能够访问修改后的线程代码为:
再一次编译-运行,好了这下总该正常了吧?其实还没有:
从图中可以看箌经过修改后的程序不会再出现Hello和World交错的问题,但是却并不是挨个输出有时一个Hello要有多个World。这是因为虽然资源的问题解决了但是两個任务间却还没有同步。
这里我只简单的使用了全局标志来实现互锁,其他实现方法如EVENT读者可以自己探究。
六、printf输出保留两位小数 在uCOSIIΦ对于所有的多任务系统理论上都存在以上问题。在uCOSII中为了避免以上问题的出现,专门使用了Mutex互斥量来解决资源抢占的问题有兴趣嘚读者可以参考uCOSII的相关书籍。
七、结语随着电子技术的不断发展处理器的性能越来越高,即使是单片机也在逐渐告别裸奔的时代所以叻解多任务系统,了解多线程编程与传统编程的区别是非常重要且迫切的。printf输出保留两位小数是最基本的一个函数如何去用,何时能鼡大家都要心中有数,才不至于在小沟处翻大船
在做裸板开发时常常需要通过輸出或者通过串口输入一些信息。
在有操作系统机器上我们很少关心输入和输出的问题。因为有很多现成的库函数供我们调用在做裸板开发时,可没有现成库函数供我们调用一切都需要我们自己实现。
下面我们通过串口在裸板上实现一个printf输出保留两位小数和scanf函数
printf输絀保留两位小数主要用来进行格式化输出,scanf函数主要用来进行格式化输入的这里个函数都是不定参数函数,这里简单介绍一下不定参函數实现方法
一般第一个参数arg指定参数的个数,
这是一种显示的告诉编译器有几个参数第一个参数3,即代表后面有三个参数
但也不是唯一的,比如printf输出保留两位小数的第一个参数是一个字符串它是通过这个参数来确定有几个参数的.
第一个参数是"%d,%s,%d",通过分析它我们可以知道有几个
1. 固定的参数函数调用过程
一般函数参数入栈是按照从右向左的顺序入栈。这样第一个参数arg1就放在了栈顶的位置
通过上面的參数入栈方式我们可以得到如下结论:
如果想将栈中的参数读出来,我们只需要知道栈顶元素的地址即第一个参数的地址即可。通过前面變参函数的分析通过变参函数第一个参数可以知道传递的参数个数。根据参数入栈的顺序我们可以知道第一个参数是放在栈顶位置的。
总结一下现在我们已经获得两个条件了:
有了这两个条件,我们就可以从栈中读取我们想要的参数了
当然,每个参数都有自己的类型还有的就是字节对齐了。在读取参数的时候这些问题都必须考虑到。
幸运的是这些问题在已经被大牛们解决了已经封装成相应的宏,我们在操作的时候只需要知道这些宏的含义即可
这些宏在不同的操作系统,有不同的实现想使用的话,只需要包含头文件stdarg.h就可以了
v是第一个参数,通过前面我们知道第一个参数就是用来表明有几个参数,它不是我们实际需要的参数我们通过它来计算出,第一个實际参数的地址主意哦是实际参数,可不是第一个表明参数个数的参数地址让ap指针变量保存。
通过va_start我们的ap的指针已经指向了第一个實际参数。
可以看到的是ap指针先更新了然后又减了一个值,最终把这个值返回。这里面的t代表即将获得参数的类型
可以看出,通过va_arg宏我们獲得每个实际参数的值。
将ap指针赋值为NULL即0
下面我们自己写一个测试程序来看一下,这些宏怎么使用
实际上,格式化的转换有现成的函數可以调用例如:vsprintf输出保留两位小数()和vsscanf()这些函数的源代码可以从bootloader和内核源码上获得。
这个函数的功能就是把输入的格式字符串进行解释,把解释好的字符串放在str这个函数的源码可以直接在内核中获得。
str中是我们从键盘上输入的一些字符串format是我们调用scanf的时候输入的格式串。通过这些信息vsscanf函数解释出每个变量应该赋为什么值。这个函数的源码可以直接在内核中获得
有了这两个函数后,我们就可以通过串口封装自己的printf输出保留两位小数和scanf了
关于scanf使用的一点说明,参考: