【问题标题】:Misunderstanding of memcpy & mmapmemcpy & mmap 的误区
【发布时间】:2018-04-25 06:40:39
【问题描述】:

我需要在进程之间使用共享内存,我找到了一个示例代码here。首先,我需要学习如何创建一个共享内存块并在其中存储一个字符串。为此,我使用了以下代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

void* create_shared_memory(size_t size) {
  // Our memory buffer will be readable and writable:
  int protection = PROT_READ | PROT_WRITE;

  // The buffer will be shared (meaning other processes can access it), but
  // anonymous (meaning third-party processes cannot obtain an address for it),
  // so only this process and its children will be able to use it:
  int visibility = MAP_ANONYMOUS | MAP_SHARED;

  // The remaining parameters to `mmap()` are not important for this use case,
  // but the manpage for `mmap` explains their purpose.
  return mmap(NULL, size, protection, visibility, 0, 0);
}



int main() {
  char msg[] = "hello world!";

  void* shmem = create_shared_memory(1);
  printf("sizeof shmem: %lu\n", sizeof(shmem));
  printf("sizeof msg: %lu\n", sizeof(msg));
  memcpy(shmem, msg, sizeof(msg));
  printf("message: %s\n", shmem);

}

输出:

sizeof shmem: 8
sizeof msg: 13
message: hello world!

在主函数中,我正在创建 1 字节 共享内存块 (shmem) 并尝试在其中存储 13 字节 信息 (char msg[])。当我打印出shmem 时,它会打印整个消息。我期待,它只打印出 1 字节 消息,在这种情况下只是 "h"。或者它可能会在编译时给出关于内存大小的错误。

问题是我在这里错过了什么?还是有执行问题? memcpy 在这里重叠吗?感谢您提供任何简短的解释。

提前致谢。

【问题讨论】:

  • 底层操作系统机制不对一个字节大小的块进行操作。事实上,您的计算机中几乎没有任何功能。
  • @pvg 那么如果我分配 128 字节并尝试在其中存储 129 字节数据(129 字符),它是如何工作的?它会引发错误吗?
  • @ErselEr 这在mmap 的文档中有所描述,您应该查看该文档。 “如果 offset 或 len 不是 pagesize 的倍数,则映射区域可能会超出指定范围。超出映射对象末尾的任何扩展都将被零填充。”
  • 我认为你必须用\0填充共享内存的所有位置

标签: c mmap memcpy


【解决方案1】:
  1. printf("message: %s\n", shmem); 中,%s 说明符表示要打印从shmem 开始的“字符串”。为此,字符串是以空字符结尾的字符序列。所以printf 打印它在shmem 找到的所有字节,直到空字符。要将其限制为最多一个字符,您可以改用%.1s,也可以使用printf("message: %c\n", * (char *) shmem); 显式打印一个字符。

  2. 当您使用mmap 分配内存时,系统以页为单位使用内存。页面的大小因系统而异,但通常为 512 或 4096 字节,而不是 1。mmap 的标准规范仅保证提供您请求的字节数。除此之外,可能还有其他可访问的字节,但您不应依赖它们可用。 (即使它们看起来暂时可用,当您的程序临时换出内存时,系统可能不会将它们保存到磁盘,因此当您的程序被带回内存继续运行时,它们不会被恢复。)

  3. sizeof(shmem) 提供shmem 的大小,它是一个指针。所以它提供了指针的大小,在现代系统上通常是四或八字节。它不提供shmem 指向的事物的大小。

  4. 1234563
  5. memcpy(shmem, msg, sizeof(msg)); 将 13 个字节(msg 的大小)复制到 shmem。这 13 个字节是“hello world!”最后是一个空字符(值 0)。 memcpy 除了您传递的长度参数外,无法知道源或目标的长度。所以它会复制sizeof(msg) 字节。它不将自身限制为shmem 指向的内存大小。传递正确的长度是你的工作。

要回答有关如果您使用的字节数超过 mmap 提供的字节数会发生什么的问题,行为是未定义的。如果超出页面边界,您的程序很可能会崩溃,因为超出该地址的内存未被映射。但是您可能会将字节写入内存中您不希望的位置,这可能会导致各种事情发生,因为它可能会损坏您的程序需要正确执行的代码或数据。

在这种情况下,您没有写超出映射内存。您要求 13 个字节,并且可能得到 4096(或系统上的任何一页)。然后你将这 13 个字节复制到缓冲区并打印出来。所以一切都“奏效了”。

【讨论】:

  • 在文件支持的映射的情况下,写入最后一页的多余容量被定义为什么都不做(读取将给出零)。在匿名映射的情况下(如问题所示),什么标准说您可以使用最后一页的多余容量?
  • @JohnZwinck:我的错误,你对mmap 的看法是正确的。我会更新的。
  • 我不会在这种情况下使用“未定义行为”一词,因为它很容易与 C 标准未定义行为最常用的方式混淆,这不是示例. Posix 文档似乎喜欢“依赖于实现”,这在清晰度(甚至可能是准确性)上并没有很大的飞跃,但至少是不同的。
  • @pvg:我不明白为什么人们在这方面把 C 标准放在首位。为了知道一个程序会做什么,你需要知道它运行的硬件、运行它的操作系统、编译它的编译器、链接的库等等。关于链接器和其他构建它的工具。所有这些都是先决条件。如果可以从先决条件到最终结果进行逻辑推论,则定义了行为。否则,不定义。 C 标准不是其中的特殊部分。
  • 这与人们“将 C 标准放在首位”没有太大关系。术语的发展独立于您或我的偏好,此时 UB 具有与其在标准中的使用相关的特定含义。如果在类似的上下文中使用该术语但表示其他含义,则会引入一个可以避免的歧义和潜在的混淆点。就是这样。
【解决方案2】:

您的代码违反了mmap() 的约定,将超过 1 个字节写入请求的大小为 1 字节的内存映射中。

但是,正如您所发现的,它有时可能适用于某些系统。这可能是因为一页的大小(在内存映射中)是例如4 KB。所以也许映射比请求的要大。尽管如此,你还是没有权利像以前那样使用它。

所以,别这样了。


您询问这是否应该是编译错误。答案是否定的:编译器没有像mmap() 这样的每个库例程的特殊情况。它不知道mmap()size 参数意味着返回的指针只对那么多字节有效。静态分析器可能会解决这个问题,但编译器不会这样做。

【讨论】:

  • OP 看到他们所看到的结果的根本原因是他们误解了如何管理数据长度。值得注意的是,printf%s 使用空字符来停止,而不是字符串长度的其他知识,memcpy 使用给定的长度,而不是与其其他参数关联的任何长度。 “未定义的行为”不是 OP 看到的结果的原因。 (“未定义的行为”永远不可能是任何事情的原因——因为它没有定义,它只能解释为什么不能保证代码以某种方式运行,而不是为什么它确实如此以某种方式。)
  • 请注意,您首先告诉 OP 他们“调用”了未定义的行为,但您没有解释他们的 memcpy 写了 13 个字节,而不是 1,或者他们的 printf 使用了找到结束的字节,而不是缓冲区大小的其他知识。很明显,OP 认为memcpy 和/或printf 会将使用的长度限制为1。鉴于这种误解,他们无法诊断出行为未定义的事实。只要他们相信他们正在复制和打印一个字节,他们就相信他们没有超出映射的缓冲区。
猜你喜欢
  • 1970-01-01
  • 2012-10-27
  • 1970-01-01
  • 1970-01-01
  • 2019-03-21
  • 1970-01-01
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多