【问题标题】:Pass pointer to guaranteed zeroed memory将指针传递给保证归零的内存
【发布时间】:2010-08-15 14:44:35
【问题描述】:

我需要将文件中不同大小的记录归零。为此,我目前正在分配虚拟记录,memset将它们归零,并将它们传递给写入函数。

是否有一些区域可以保证总是归零(并且大小足够大),而我可以指向它,从而无需重复分配和归零内存?

【问题讨论】:

    标签: c optimization malloc memset


    【解决方案1】:

    如果记录大小有合理的上限,请分配一个包含零的全局只读变量。 (因为它是一个静态持续时间对象,它会自动初始化为零。)

    const unsigned char zero_filled_buffer[MAX_RECORD_SIZE]; /*at file scope*/
    

    如果写入函数是 C fwrite 或 POSIX write 或其他函数,您可以(必须,对于 write)在循环中调用它,因此缓冲区不必像最大的记录,和你一次写的最大块一样大。

    在典型的托管实现下,这样的变量将在您的可执行文件中占用零空间。 添加:请注意,就 C 标准而言,上面的声明与 const unsigned char zero_filled_buffer[MAX_RECORD_SIZE] = {0}; 完全相同,但是如果您显式添加 = {0},某些编译器(包括 gcc)会在可执行文件中包含零但如果您不使用初始化程序,则不会。

    具有虚拟内存的系统上的智能程序加载器可以利用虚拟内存系统为所有此类对象使用物理 RAM 的单个共享只读零填充页面;不知道有没有实际操作。 添加:例如,Linux (Debian lenny amd64) 没有。

    另一种 POSIX 方法是 mmap 文件并调用 memset 以清零缓冲区。

    【讨论】:

    • 其实我相信我可以把= {0}放在上面
    • @Matt Joiner:可以,但不会有任何区别。
    • @Job:就 C 标准而言,它没有任何区别。但我刚刚测试过,它确实对 gcc 有影响。
    • 如果你显式初始化它,大多数编译器会将对象写入二进制文件。这不仅仅是愚蠢,而是保持与系统级代码中使用的一些传统技巧的兼容性,其中作者有特定于实现的原因希望对象位于 .data.bss 中。
    • 您实际上也不必将其声明为文件范围标识符 - 如果您想在归零函数中声明它,只需使用 static const ...
    【解决方案2】:

    calloc

    calloc() 函数应为nelem 元素的数组分配未使用的空间,每个元素的字节大小为elsize。空间应初始化为所有位0

    或者(我没有尝试过),如果您根本不想要 任何 分配,您可以 open 和/或 mmap /dev/zero 并读取 record_size 块并将它们写入要覆盖记录的文件中。

    【讨论】:

    • @Matt 我怎么没抓住重点?一旦你找出你想要的最大的零块,你可以用calloc分配一次并使用它。
    【解决方案3】:

    至少在linux上通过mmap()分配内存会给你一个零填充的缓冲区。 缺点是您不能只分配所需的内存,而只能分配页面大小的倍数

    #include <unistd.h>
    long sz = sysconf(_SC_PAGESIZE);
    

    【讨论】:

      【解决方案4】:

      是的,只需为任何这些记录分配一个足够大的块,然后将其归零一次。每次都将该块的地址传递给您的写入函数,其中记录的大小是您实际想要清零的记录。将缓冲区传递给写入不会使其过期或任何事情。请注意, write 也不会释放您传递的缓冲区;这取决于你。

      【讨论】:

        【解决方案5】:

        如果您想要一个始终为零的大内存区域,您应该自己分配它并将其 memset 为零。没有解决这个问题,但你应该只需要做一次。确保它至少与您在任何时候需要的最大归零内存一样大。

        然后,当您需要将指针传递给归零内存时,您可以将指针传递给您分配的这个块。

        【讨论】:

          【解决方案6】:

          如前所述,您只需分配一次您将需要的最大区域;您可以在任何需要该大小或更小的区域时传递它。

          在大多数实现中,地址空间的任何部分都没有映射到 RAM,但在读取时会无害地读取零。拥有这样的东西可能很好,但我不知道有一个。

          在一些嵌入式系统中,我编写了闪存写入例程,因此,如果给定一个空指针,它们将假定源数据(取决于应用程序)都是 FF,因为有时我确实需要清除一个文件的一个块,并且让最终的写入代码处理空指针情况意味着查找和分配闪存块的代码可以在写有意义的数据情况和写空白数据的情况之间共享。需要注意的是,如果将写入拆分为多个部分,则不得在将空指针传递给 I/O 写入之前将偏移量添加到空指针。

          【讨论】:

            【解决方案7】:

            使用系统信息API获取系统页面大小(我只是不记得确切的名称),分配1页内存,将其设置为零,一遍又一遍地顺序写入。

            【讨论】:

            • 不要将其设置为零。它自然会为零,并且只要您保持这种状态(未写入)就不会使用任何物理内存,但是如果您向其写入任何内容,它将使用物理内存。最好是mmap 它是只读的。
            • @R:我不太了解内存分配函数的基础结构,但我认为分配一页不会影响任何事情。
            【解决方案8】:

            write 函数的速度将比 memset 慢几个数量级。

            分析它!

            * 即使使用闪存驱动器

            【讨论】:

            • 哦,我不反对,这是我想避免的分配/解除分配。
            【解决方案9】:

            这是保证工作的运行时可能性(使用gcc zeroed_mem_region.c -Wall -std=gnu99 编译):

            #include <sys/mman.h>
            #include <assert.h>
            #include <stdio.h>
            
            size_t const zeroed_size = 512;
            char const *zeroed;
            
            int main()
            {
                zeroed = mmap(
                        NULL,
                        zeroed_size,
                        PROT_READ,
                        MAP_PRIVATE|MAP_ANONYMOUS,
                        -1,
                        0);
                printf("zeroed region at %p\n", zeroed);
                for (size_t i = 0; i < zeroed_size; ++i) {
                    assert(zeroed[i] == 0);
                }
                printf("testing for writability\n");
                ((char *)zeroed)[0] = 1;
                return 0;
            }
            

            请注意,用于测试的归零是 char const *,实际上这将是 void const *

            优点

            • 避免使用 malloc 分配器
            • 保证区域不可写(生成SIGSEGV
            • 比使用 malloc 更快
            • 不会在可执行文件中添加废话
            • 不需要 memset 步骤(检查 mmap(2)

            缺点

            • Unix/Linux 特定(自 Linux 2.4 起匿名映射)
            • 减少编译器优化的可能性(尽管这方面显然不存在)

            【讨论】:

            • 快于 malloc 对几 kB 来说并不重要。写入时的 SIGSEV 优于 const zeroed[](gcc 在我的机器上可写);另一方面,如果您直接分配,const zeroed[] 会在 gcc 中出现编译时错误。可执行文件中没有废话也适用于静态持续时间const zeroed[]。另一个专业人士能够在运行时选择尺寸。 MAP_ANON 不是 POSIX,但在现代 unice 上广泛可用。 const zeroed[] 和 mmap 都不够聪明,无法在我的机器 (Linux/amd64) 上一遍又一遍地分配相同的物理页面(不过对于 512B 来说不是问题)。
            • 我接受这个答案有一段时间了,因为我认为它解决的编译器缺陷值得关注。
            猜你喜欢
            • 2012-02-22
            • 2021-07-12
            • 2010-10-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-02-13
            • 2018-07-10
            • 1970-01-01
            相关资源
            最近更新 更多