一些 C 函数,痛苦的移植性

2015年11月17日 17:09:46

有一个很好的链接:Standard C 语言标准函数库速查。不在其中的自然就不是 C 标准库中的函数,不过有一点点瑕疵,这个速查的搜索功能不好用。

  1. 即便是标准库中的函数,在不同平台也是会有差异的(但标准规定的部分应该是相同的)。比如:fflushfopen
  2. 不在标准库中的函数:不同的平台使用了相似(甚至相同)的名字实现了相似的功能,所谓“相似”则必然有不同,其中的区别就是我们编码过程中的陷阱!(极端一点)可以认为只是不同平台不同实现选择了相同的名字而已。比如:snprintfaccess 并不是标准 c 标准或者 c++ 标准中规定的函数。

snprintf 在 VC 和 GCC 上的不同

printfsprintf 属于 I/O库 函数,snprintf 函数并不是标准 c/c++ 中规定的函数,但是在许多编译器中,厂商提供了其实现的版本。其声明同样放在了声明 “standard input & output”(标准输入输出)的 <stdio.h> 中。 在 gcc 中,该函数名称就 snprintf;而在 VC 中称为 _snprintf

在windows下:

  1. VC 中的 _snprintf 的 count 参数(第二个参数)表示,会向 buff 中写入 count 个字符,不包括 '\0' 字符;
  2. 并且不会在字符串末尾添加 '\0' 符;
  3. 并且,如果字符串(第三个参数)超过 count,函数返回 -1 以标志可能导致的错误;

上述描述如有异议或描述不清,请参考 微软文档

Let len be the length of the formatted data string, not including the terminating null.
if len < count, len characters are stored in buffer, a null-terminator is appended, and len is returned.
if len = count, len characters are stored in buffer, **no** null-terminator is appended, and len is returned.
If len > count, count characters are stored in buffer, no null-terminator is appended, and a negative value is returned.

windows-snprintf

在linux下:

参考 snprintf 函数用法

  1. gcc 中的 snprintf 函数的 count 参数(第二个参数)表示,向 buff 中写入 count 个字符,包括 '\0' 字符;
  2. 并且,若成功则返回欲写入的字符串长度,若出错则返回负值;

linux-snprintf

snprinf 在 C++11 中是不是规范化了?是的

C99/C++11 起,标准规范了 snprintf 函数的定义,msvc 对此特性的支持比较晚:

Beginning with the UCRT in Visual Studio 2015 and Windows 10, snprintf is no longer identical to _snprintf. The snprintf function behavior is now C99 standard compliant.摘自微软

access 函数,注意参数含义

两者的头文件自然不同。而且两者的参数列表和类型虽然相同,但是第2个参数取值具体的含义却不同

慎用 fflush(stdin) 清空输入缓存流

怎么说这个函数呢,如果大家在C标准函数库里查找会发现这个是其中之一,但为什么还要把它放在这呢?来看一下 C99 对 fflush 函数的定义:

int fflush(FILE *stream);

如果 stream 指向输出流或者更新流(update stream),并且这个更新流最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至宿主环境(host environment)写入文件。否则,它的行为是未定义的。

原文如下:

int fflush(FILE *stream);

If stream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

其中,宿主环境可以理解为操作系统或内核等。 由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。

  • gcc 严格执行了标准,所以在 linux 下使用 flush(stdin) 是没有任何效果的,是不确定的,是不安全的;

  • 但是在 VC 中使用 flush(stdin) 却可以清空输入缓冲。 看一下MSDN 文档,其中清楚地写着:

    fflush on input stream is an extension to the C standard(**fflush 操作输入流是对 C 标准的扩充**)。

fflush函数有什么作用? 这篇帖子里的用法只能在 windows 下有效;在 linux 下是什么样子的并不能保证。

windows 下使用 fflush

linux 下使用 fflush

上图和windows下 注释掉fflush() 效果一致。

windows 下注释掉 fflush

普遍用法

fflush(stdout):

  • 头文件 :#include <stdio.h>
  • 函数定义:int fflush(FILE *stream)
  • 函数说明:fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中。如果参数stream为NULL, fflush()会将所有打开的文件数据更新。
  • 返回值 :成功则返回0, 失败返回EOF, 错误代码存于errno中

用途:在使用多个输出函数连续进行多次输出时,有可能发现输出错误。因为下一个数据在上一个数据还没输出完毕,还在输出缓冲区中时,下一个 printf 就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误。 在 printf() 后加上 fflush(stdout); 强制马上输出,例子:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<unistd.h>//unix环境
int main()
{
printf("hello");
fflush(stdout);
fork();//fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事
return 0;
}

这样输出一个hello

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello");
// fflush(stdout);
fork();
return 0;
}

这样会输出两个hello

fopen() 函数

函数用法 C语言的fopen函数(文件操作/读写)

在 POSIX 系统,包含 Linux 下都会忽略 b 字符。由 fopen() 所建立的新文件会具有 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666) 权限,此文件权限也会参考 umask 值。

二进制和文本模式的区别:

  • 在 windows系统中,文本模式下,文件以 \r\n 代表换行。若以文本模式打开文件,并用 fputs 等函数写入换行符 \n 时,函数会自动在 \n 前面加上 \r。即实际写入文件的是 \r\n
  • 在类 Unix/Linux 系统中文本模式下,文件以 \n 代表换行。所以 Linux 系统中在文本模式和二进制模式下并无区别。

文件操作类型:

打开方式 说明
r 以只读方式打开文件,该文件必须存在。
r+ 以读/写方式打开文件,该文件必须存在。
rb+ 以读/写方式打开一个二进制文件,只允许读/写数据。
rt+ 以读/写方式打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则长度清为0,即该文件内容消失,若不存在则创建该文件。
w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)。
a+ 以附加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符 不保留)。
wb 以只写方式打开或新建一个二进制文件,只允许写数据。
wb+ 以读/写方式打开或建立一个二进制文件,允许读和写。
wt+ 以读/写方式打开或建立一个文本文件,允许读写。
at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。

atoi() C标准库函数、itoa() 非C标准库函数

如果是在 windows 平台下开发,上面两个 intchar* 字符串相互转化的函数应该都用过。想当然的也会认为这是一对函数,“夫妻双双……”。其实他俩的出身完全不同。

在笔记开篇给出了一个查询 C 标准库的链接,在 <stdlib.h> 常用系统函数中列出了以下:

字符串函数

是不是觉得自己可以举一反三,猜到将浮点型转为字符串可以用函数 ftoa(),将整型数转为字符串可以用 itoa()……但你查遍 C 的标准库是不是也没找到你以为的 itoa() 函数。事实也正是如此!itoa并不是一个标准的C函数,它是Windows特有的。