read 函数第三个参数是不带符号的整型,返回值是带符号的整型,这么设计感觉不合理啊

2020-05-12 10:35:40 +08:00
 lolcat

ssize_t read(int fd, void *buf, size_t count);

返回值是带符号的 ssize_t,代表此次 read 实际读取的字节数。 第三个参数是不带符号的 size_t,代表此次希望读取的字节数。

显然 size_t 的最大值是 ssize_t 的两倍,可以假设 size_t 的最大值是 255,ssize_t 的最大值是 127,如果在 read 函数的第三个参数中填 200,是不是代表 read 最多只能读取 127 字节数据?如果是的话,感觉 linux 这么设计有点不合理啊,感觉和所见即所得原则有点违背。

3202 次点击
所在节点    Linux
15 条回复
guyeu
2020-05-12 10:57:01 +08:00
可能是 count 不允许小于 0,但是返回值有可能小于 0,才这么设计 API 吧
amaranthf
2020-05-12 10:57:15 +08:00
我觉得应该是有两个考量吧:
一是返回的时候需要一个值用来表示“错误”,而 0 和正整数都有其他含义,为了避免再增加一个参数,就只能用负数表示了;
二是设计者可能认为你没有一下子读出 2GB 以上的东西的需求——在 32 位机器上这的确是很难的事情,而 64 位的话 ssize_t 也要变大,就更不可能了。
事实上在 Linux 下面,read 函数内部是限制了一次读取的最大值的,这个值是 0x7ffff000 (包括 64 位下面)。
CRVV
2020-05-12 12:27:40 +08:00
read(2) 里有说,
On success, the number of bytes read is returned (zero indicates end of file)
On error, -1 is returned, and errno is set appropriately.
那么返回值必须是有符号的

另外,read 不保证把传进去的 buf 读满,给一个长度 200 的,只读取 127 字节或者只读取 1 字节都是可以的。

这个 API 也不是 Linux 设计的,这是 POSIX 里定义的 API,Linux 只能照着实现。
momocraft
2020-05-12 12:45:30 +08:00
"所见即所得原则" 是什么
leido
2020-05-12 12:47:43 +08:00
上古 API,能用就行
AngryMagikarp
2020-05-12 12:54:36 +08:00
首先 read 本来就不保证能读取到 count 长度的数据,你传再大的 count,他也可以返回一个字节给你。

其次,哪怕是一次读取有符号整数最大值的数据,也足够了。即使把返回值换成无符号数,你依然可以说,一次性最多读取无符号数的最大值,完全不够啊,杠精天下第一。
AngryMagikarp
2020-05-12 13:00:26 +08:00
According to POSIX.1, if count is greater than SSIZE_MAX, the result is implementation-defined; see NOTES for the upper limit on Linux.

On Linux, read() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552) bytes, returning the number of bytes actually transferred. (This is true on both 32-bit and 64-bit systems.)

在 Linux 上,即使 64 位系统中也最多一次性读取 2G 数据。反正我读取文件或者网络 IO,一般情况下都是 4096 读。
AngryMagikarp
2020-05-12 13:08:01 +08:00
另外什么是所见即所得。我用 linux 这么久了,早期还看了很多关于 unix 历史和设计理念的书,从来没见过所见即所得这个概念。我只知道富文本编辑器所见即所得。
qakito
2020-05-12 13:16:50 +08:00
1. 常规场景中使用到的 buffer 远小于 SSIZE_MAX
如果你的代码里出现了一个 4G 大小的 buffer,更应该审视代码的合理性
2. API 只需保证你的 buffer 不溢出,不保证你的 buffer 全部塞满
AngryMagikarp
2020-05-12 13:22:11 +08:00
系统调用只是提供一些基础功能,并不是为了某些特定场景设计的。实际中如果真的要一次性读取大数据,也可以简单地分多次读取实现。

Go 语言中 ioutil.ReadAll 函数甚至是每次读取 512 长度,通过循环读取实现的。
nightwitch
2020-05-12 13:26:54 +08:00
posix 标准诞生的时候硬件规格限制了不可能一次性读满 ssize_t 的
Unix/Linux 没有所见即所得这个原则
weyou
2020-05-12 19:39:55 +08:00
读取多少字节函数自己能决定,所以只要函数自己保证返回值不会引起符号溢出就可以了
lolcat
2020-05-12 21:19:13 +08:00
@AngryMagikarp 没看懂帖子吧,我说实际读取的最大字节数只有计划读取的最大字节数的一半,就像我只能吃一碗饭,我却用一个盆打饭,没想表达一次读取的字节数太少,怎么就杠了?
AngryMagikarp
2020-05-12 22:29:15 +08:00
@lolcat 这里的 count 和返回值并没有对应关系,他们之间的唯一关系是 count 大于等于返回值,那么 count 比返回值大多少是无所谓的。
真正和 count 有关系的其实是 buf,count 表示的就是 buf 的大小,buf 的大小一定不会是负的。而且你去看看 malloc 的参数也是 size_t,这两者是统一的。
msg7086
2020-05-13 03:17:59 +08:00
C 语言里的数值就是这么设计的,有符号的就是无符号的一半。
你可以理解成 count 只能输入 0-2147479552,而返回值只能是-1-2147479552,范围是基本一致的。

你说
「可以假设 size_t 的最大值是 255,ssize_t 的最大值是 127,如果在 read 函数的第三个参数中填 200 」
而其实 read 函数规定你只能填 120,所以 255 也好 200 也好 127 也好都是无关的。

顺便 size_t 在 64 位下最大可以取值 18446744073709551615,即使这样,read 也只会读取 2147479552 字节。
你用一个宇宙去打饭,也只给你一小碗。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/670828

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX