使用 GNU/Linux read() 函数的正确方法

proper way to use GNU/Linux read() function

本文关键字:函数 方法 GNU Linux read 使用      更新时间:2023-10-16

在GNU/Linux的手册页中,read函数

描述如下:
ssize_t read(int fd, void *buf, size_t count);

我想使用此功能从套接字或串行端口读取数据。如果count大于 1,则函数参数中提供的指针将指向从内存中的端口读取的最后一个字节,因此指针递减是将指针指向数据的第一个字节所必需的。这是危险的,因为在像C++这样的语言中使用它,并根据容器的大小和空间需求对容器进行动态内存分配,可能会在从函数返回时损坏数据read()。我想使用 C 样式数组而不是指针。这是正确的方法吗?如果没有,正确的方法是什么?我使用的编程语言是C++。

编辑:

导致所述情况的代码如下:

QSerialPort类用于配置和打开具有以下参数的端口:

  • 波特率 115200
  • 8 个数据位
  • 无奇偶校验
  • 一个停止位
  • 无流量控制

对于读取部分,只要涉及堆栈溢出,读取就完全像这样执行:

包含多个structstd::vector,定义方式如下:

struct DataMember 
{
    QString name;
    size_t  count;
    char *buff;
}

然后在一段时间内循环,直到到达上述std::vector的末尾,根据所述struct的成员变量执行count read(),并将数据存储在同一struct的buff中:

ssize_t nbytes = read(port->handle(), v.at(i).buff, v.at(i).count);

然后将数据打印在控制台上。在我的测试用例中,只要数据是一个字节,打印的值是正确的,但对于多个字节,显示的值是从端口读取的最后一个值加上一些垃圾值。我不知道为什么会这样。请注意,当char *buff更改为 char buff[count] 时,将获得正确的结果。

如果计数大于 1,则函数参数中提供的指针将指向从内存中的端口读取的最后一个字节

不。指针按值传递给 read() 方法,因此,无论计数如何,调用后的值都完全不可能与以前有任何不同。

因此,指针递减对于将指针指向数据的第一个字节是必要的。

指针已指向数据的第一个字节。无需递减。

这是危险的,因为在像C++这样的语言中使用它,并根据容器的大小和空间需求对容器进行动态内存分配,可能会在 read() 函数返回时损坏数据。

这都是基于不可能的胡说八道。

你误会了这一切。

在我的测试用例中,只要数据是一个字节,打印的值是正确的,但对于多个字节,显示的值是从端口读取的最后一个值加上一些垃圾值。

从 read(2) 手册页:

成功后,返回读取的字节数(零表示文件结束), 并且文件位置按此数字前进。 如果此数字是 小于请求的字节数;例如,这可能会发生,因为更少 字节现在实际上可用(可能是因为我们接近文件末尾,或者 因为我们是从管道或终端读取的),或者因为 read() 被中断了 通过信号。 出错时,返回 -1,并正确设置 errno。 在这种情况下,它 未指定文件位置(如果有)是否更改。

对于管道、套接字和字符设备(包括串行端口)和阻塞文件描述符(默认),读取实际上不会等待全部计数。在您的情况下,read() 块,直到串行端口上出现一个字节并返回。这就是为什么在输出中第一个字节是正确的,其余的都是垃圾(未初始化的内存)。如果需要完整计数,则必须在 read() 周围添加一个循环,该循环重复直到读取了计数字节。

我不知道为什么会这样。

但我知道。 char *只是一个指针,但该指针需要初始化为某些内容,然后才能使用它。如果不这样做,您就会调用未定义的行为,一切都可能发生。

代替size_t count;char *buff元素,您应该只使用std::vector<char>,在进行读取调用之前,将其大小调整为要读取的字节数,然后获取该向量的第一个元素的地址并将其传递给读取:

struct fnord {
    std::string name;
    std::vector data;
};

并像这样使用它; 请注意,使用 read 需要一些额外的工作才能正确处理信号和错误条件。

size_t readsomething(int fd, size_t count, fnord &f)
{
    // reserve memory
    f.data.reserve(count);
    int rbytes = 0;
    int rv;
    do {
        rv = read(fd, &f.data[rbytes], count - rbytes);
        if( !rv ) {
            // End of File / Stream
            break;
        }
        if( 0 > rv ) {
            if( EINTR == errno ) {
                // signal interrupted read... restart
                continue;
            }
            if( EAGAIN == errno
             || EWOULDBLOCK == errno ) {
                // file / socket is in nonblocking mode and
                // no more data is available.
                break;
            }
            // some critical error happened. Deal with it!
            break;
        }
        rbytes += rv;
    } while(rbytes < count);
    return rbyteS;
}
<小时 />

看看你的第一段胡言乱语

如果计数大于 1,则函数参数中提供的指针将指向从内存中的端口读取的最后一个字节

是什么让你这么认为?这不是它的工作原理。很可能您传递了一些未正确初始化的无效指针。任何事情都可能发生。

因此,指针递减对于将指针指向数据的第一个字节是必要的。

不。这不是它的工作原理。

这是危险的,因为在像C++这样的语言中使用它,并根据容器的大小和空间需求对容器进行动态内存分配,可能会在 read() 函数返回时损坏数据。

这不是它的工作原理!

C 和 C++ 是一种显式语言。一切都发生在众目睽睽之下,没有(程序员)明确要求,什么都不会发生。没有请求此操作,则不会分配内存。它可以是显式new,一些RAII,自动存储或使用容器。但是在 C 和 C++ 中没有发生任何"突然"的事情。C 语言中没有内置的垃圾收集^1,也没有C++。对象不会在内存中移动或调整大小,除非你显式地将某些内容编码到程序中来实现这一点。

<小时 />

[1]:您可以使用一些 GC 库,但这些库永远不会踩到正在执行的代码可以访问的任何内容。本质上,C 和 C++ 的垃圾回收器库是内存泄漏检测器,它将释放正常程序流无法再访问的内存。