【问题标题】:Reliable way to place char directly after array在数组之后直接放置 char 的可靠方法
【发布时间】:2017-06-02 15:50:46
【问题描述】:

我正在使用以下代码从套接字读取:

char buf[4097];
int ret = read(fd, buf, sizeof(buf) - 1);
buf[ret] = 0x0;
std::cout << buf << "\n";

但是,我不喜欢其中需要 4097sizeof(buf) - 1。就是那种很容易忘记的东西。所以我想知道,是否有一些好方法可以强制编译器将 0x0 直接放在数组之后的堆栈上?

我想要的是类似的东西

char buf[4096];
char _ = 0x0;
int ret = read(fd, buf, sizeof(buf));
buf[ret] = 0x0;
std::cout << buf << "\n";

但我不知道如何强制编译器不要,但介于两者之间(afaik #pragma pack 仅适用于结构,不适用于堆栈)。

【问题讨论】:

  • 喜欢...char buf[4097] = {0};?这叫“初始化”……
  • char _ 无效。你不能这样命名字符。
  • 如果您使用 C++ 编程,请仅使用该标签,即使部分问题可能与 C 有关。C 和 C++ 大部分是两个 非常 不同的语言,具有非常不同的语义。
  • @LethalProgrammer 单个下划线是合法符号。两个,三个或更多。唯一的限制是在所有范围内为编译器和标准库保留以两个下划线开头的名称。
  • 如果数据是字符串(并且您的代码暗示它是),请使用fgets。但是,是的,动机并不令人信服。

标签: c++ c++11


【解决方案1】:

我会保持简单:

ssize_t read_and_put_0(int fd, void *buf, size_t count)
{
    ssize_t ret = read(fd, buf, count - 1);
    if (ret != -1) // Or `if (ret <= 0)`
        ((char *)buf)[ret] = 0x0;
    return ret;
}

// ...

char buf[4097];
read_and_put_0(fd, buf, sizeof buf);

【讨论】:

  • 我不明白为什么它不起作用(如果_ 就在buf 之后);如果ret == sizeof(buf),它将用数据填充整个缓冲区,buf[ret] = 0 会将_ 设置为0x0。所以它应该可以正常工作而不会崩溃......我想我错过了一些东西:/
  • @Paladin _ 紧随 buf 之后是源代码中的 after。编译器可能无法如您所愿地在内存中找到这两个变量。
  • @Paladin 如果ret &lt; sizeof buf 怎么办?想象一下read 将 1 个字节读入buf[0] 并返回1。终止的0x0 应该放在buf[1] 中,这就是buf[ret] = 0x0; 所做的。另一方面,char _ = 0x0; 本质上是buf[sizeof buf]
  • @HolyBlackCat read 最好与size_t count 一起使用,如果count==0 进行处理的测试..
  • @Paladin 哦。我想将 _ 初始化为 0x0 让我很困惑。
【解决方案2】:

我不喜欢那里需要4097sizeof(buf) - 1

简单就是美:

constexpr std::size_t size = 4096;
char buf[size + 1];
int ret = read(fd, buf, size);
buf[ret] = 0x0;

您可以准确指定所需的尺寸,无需手动添加。而且既不需要 sizeof,也不需要减 1。

在我看来,记住终止符的 + 1 比记住声明一个单独的字符对象更容易 - 无论如何不能强制它直接位于数组之后。

也就是说,读取文本文件的方法比read 更容易出错。

【讨论】:

    【解决方案3】:

    未指定的不同变量的值在内存中的相对位置。事实上,有些变量可能根本不存在于内存中。如果要确保内存中数据的相对布局,请使用结构或类。例如:

    struct {
        char buf[4096];
        char term;
    } tbuf = { { 0 }, 0 };
    
    int ret = read(fd, tbuf.buf, sizeof(tbuf.buf));
    if (ret >= 0 && ret < sizeof(tbuf.buf)) {
        tbuf.buf[ret] = '\0';
    }
    

    保证结构的成员在内存中的布局顺序与声明它们的顺序相同,因此您可以确信故障安全终止符tbuf.term 将跟随tbuf.buf。但是,您不能确信之间没有填充。此外,这只是一个故障保险。您仍然需要编写空终止符,如图所示,以防出现短读。

    此外,即使tbuf 的表示肯定比其buf 成员大至少一个字节,它仍然会产生UB 以在其边界之外访问tbuf.buf。总体而言,我认为您不会因此获得太多收益。

    【讨论】:

    • 如果我将它与#pragma pack(push, 1) 结合以确保bufterm 之间没有填充,它还会是UB吗?
    • @Paladin,是的。访问超出其边界的数组会产生 UB,句点。这可能会以一种无害的方式表现出来,但这又是未定义的。
    【解决方案4】:

    HolyBlackCats 答案的替代方案,只要您拥有数组而不是指向某个数组的指针,就不需要提供 size 参数。

    template <size_t N> ssize_t read_and_put_0(int fd, char (&buf)[N]) {
        ssize_t ret = read(fd, buf, N - 1);
        if(ret != -1) // Or `if (ret <= 0)`
            buf[ret] = 0x0;
        return ret;
    }
    
    char buf[4097];
    read_and_put_0(fd, buf);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-11
      • 2018-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多