是否有任何平台在fd_set上使用结构副本(对于select()或pselect())会导致问题?

Jon*_*ler 16 c unix linux posix

select()pselect()系统调用修改其参数(在" fd_set *"参数),所以输入值告诉系统文件描述符检查和返回值告诉程序员哪些文件描述符当前可用它.

如果要为同一组文件描述符重复调用它们,则需要确保每个调用都有一个描述符的新副本.显而易见的方法是使用结构副本:

fd_set ref_set_rd;
fd_set ref_set_wr;
fd_set ref_set_er;
...
...code to set the reference fd_set_xx values...
...
while (!done)
{
    fd_set act_set_rd = ref_set_rd;
    fd_set act_set_wr = ref_set_wr;
    fd_set act_set_er = ref_set_er;
    int bits_set = select(max_fd, &act_set_rd, &act_set_wr,
                          &act_set_er, &timeout);
    if (bits_set > 0)
    {
        ...process the output values of act_set_xx...
    }
 }
Run Code Online (Sandbox Code Playgroud)

(编辑删除不正确的struct fd_set引用 - 正如'R ..'所指出的那样.)

我的问题:

  • 是否存在任何平台,fd_set如图所示,对值进行结构复制是不安全的?

我担心的是,有任何隐藏的内存分配或任何意外的事情.(有宏/函数FD_SET(),FD_CLR(),FD_ZERO()和FD_ISSET()来掩盖应用程序的内部.)

我可以看到MacOS X(达尔文)是安全的; 因此,其他基于BSD的系统可能是安全的.您可以通过记录您知道答案中安全的其他系统来提供帮助.

(我确实对fd_set使用超过8192个打开文件描述符的效果有一些小问题- 打开文件的默认最大数量仅为256,但最大数量为'无限'.此外,由于结构为1 KB,复制代码并不是非常有效,但是然后运行文件描述符列表以在每个循环上重新创建输入掩码也不一定有效.也许select()当你打开那么多文件描述符时你不能这样做,尽管那是你的时候最有可能需要这些功能.)


有一个相关的SO问题 - 询问'poll()vs select()',它解决了这个问题的一组不同问题.


请注意,在MacOS X上 - 可能更常见的是BSD - 有一个FD_COPY()宏或函数,有效的原型:

  • extern void FD_COPY(const restrict fd_set *from, restrict fd_set *to);.

在尚未提供的平台上进行模拟可能是值得的.

Car*_*rum 9

既然struct fd_set只是一个普通的C结构,那应该总是很好.我个人不喜欢通过=运算符进行结构复制,因为我已经在大量平台上工作,这些平台无法访问正常的编译器内在函数集.memcpy()在我的书中,明确使用而不是让编译器插入函数调用是一种更好的方法.

从C规范,第6.5.16.1简单分配(为简洁起见,在此编辑):

以下其中一项应持有:

...

  • 左操作数具有与右侧类型兼容的结构或联合类型的限定或非限定版本;

...

简单赋值(=)中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值.

如果从另一个以第一个对象的存储方式重叠的对象读取存储在对象中的值,则重叠应该是精确的,并且两个对象应具有兼容类型的合格或非限定版本; 否则,行为未定义.

所以你去,只要struct fd_set是一个普通的C struct,你就能保证成功.但是,它确实依赖于编译器发出某种代码来执行它,或者依赖于memcpy()它用于结构赋值的任何内在代码.如果您的平台由于某种原因无法链接到编译器的内部库,则可能无法正常工作.

如果你有更多的开放文件描述符,你将不得不玩一些技巧struct fd_set.linux 手册页说:

An fd_set是固定大小的缓冲区.执行FD_CLR()FD_SET()使用值为fd负或等于或大于FD_SETSIZE将导致未定义的行为.而且,POSIX需要fd是有效的文件描述符.

如下所述,可能不值得努力证明您的代码在所有系统上都是安全的. FD_COPY()仅供此类用途使用,并且可能始终保证:

FD_COPY(&fdset_orig, &fdset_copy)&fdset_copy用一个副本替换已分配的文件描述符集&fdset_orig.


R..*_*R.. 7

首先,没有struct fd_set.它只是被称为fd_set.但是,POSIX确实要求它是结构类型,因此复制是明确定义的.

其次,在标准C下没有办法在fd_set对象中包含动态分配的内存,因为在返回之前不需要使用任何函数/宏来释放它.即使编译器具有alloca(基于堆栈的分配的pre-vla扩展),fd_set也无法使用在堆栈上分配的内存,因为程序可能会将指针传递fd_set给另一个使用FD_SET等的函数,并且分配的内存将会一旦返回呼叫者就停止有效.只有C编译器为析构函数提供了一些扩展才能fd_set使用动态分配.

总之,分配/ memcpy fd_set对象似乎是安全的,但可以肯定的是,我会做类似的事情:

#ifndef FD_COPY
#define FD_COPY(dest,src) memcpy((dest),(src),sizeof *(dest))
#endif
Run Code Online (Sandbox Code Playgroud)

或者只是:

#ifndef FD_COPY
#define FD_COPY(dest,src) (*(dest)=*(src))
#endif
Run Code Online (Sandbox Code Playgroud)

然后你将使用系统提供的FD_COPY宏(如果它存在),并且如果它缺失则只返回到理论上可能不安全的版本.

  • 请注意,答案将“dest”作为第一个参数,而 BSD 实现将“src”作为第一个参数。 (2认同)