如何检查内存块中的所有字节是否为零

How to check whether all bytes in a memory block are zero

本文关键字:字节 是否 何检查 检查 内存      更新时间:2023-10-16

我有一块固定大小的元素,比如100字节,一个接一个地放入其中,所有元素都具有相同的固定长度,所以内存看起来像这样

<element1(100 bytes)><element2(100 bytes)><element3(100 bytes)>...

在某些情况下,我需要确定某个元素的所有字节是否都设置为0字节,因为这有一个特殊的含义(我没有说这是一个好主意,但这就是我所处的情况)。

问题是,我如何有效地做到这一点。进一步:是否有一个简单的函数来完成它。对于设置字节为零,我可以使用memset或bzero,但我不知道有任何检查零的函数。

现在我使用循环检查

char *elementStart = memoryBlock + elementNr*fixedElementSize;
bool special = true;
for ( size_t curByteNr=0; curByteNr<fixedElementSize; ++curByteNr )
{
  special &= (*(elementStart+curByteNr)) == 0;
}

当然,我可以使用较大的偏移量进行循环,并用mword或其他适合的较大类型一次检查几个字节。我想这将是相当有效的,但我想知道是否有一个函数可以减轻我的负担。

显示功能:

  • !memcmp (compareBlock, myBlock, fixedElementSize)

实际上可以使用memcmp而不必分配一个零值数组,如下所示:

static int memvcmp(void *memory, unsigned char val, unsigned int size)
{
    unsigned char *mm = (unsigned char*)memory;
    return (*mm == val) && memcmp(mm, mm + 1, size - 1) == 0;
}

memcmp的标准没有提到任何关于内存区域重叠的问题。

显而易见的便携、高效的方法是:

char testblock [fixedElementSize];
memset (testblock, 0, sizeof testblock);
if (!memcmp (testblock, memoryBlock + elementNr*fixedElementSize, fixedElementSize)
   // block is all zero
else  // a byte is non-zero

在大多数实现中,库函数memcmp()将在大多数比较中使用最大,最有效的单元大小。

为了提高效率,不要在运行时设置testblock:

static const char testblock [100];
根据定义,静态变量自动初始化为零,除非有初始化式。

我真不敢相信居然没有人发这个…一个解决方案,实际上看起来像c++,而不是UB违反混叠规则:

#include <algorithm> // std::all_of
#include <cstddef>   // std::size_t
// You might only need this
bool
memory_is_all_zeroes(unsigned char const* const begin,
                     std::size_t          const bytes)
{
    return std::all_of( begin, begin + bytes,
                        [](unsigned char const byte) { return byte == 0; } );
}
// but here's this as a bonus
template<typename T_Element, std::size_t T_count>
bool
array_is_all_zeroes( T_Element const (& array)[T_count] )
{
    auto const begin = reinterpret_cast<unsigned char const*>(array);
    auto const bytes = T_count * sizeof(T_Element);
    return memory_is_all_zeroes(begin, bytes);
}
int
main()
{
    int const blah[1000]{0};
    return !array_is_all_zeroes(blah);
}

这可能不能满足一些人对效率的假设(这只是假设,直到分析),但我认为有效和习惯的代码是非常有利的。

恐怕没有自动检查内存的功能。

您可以使用|来加速for循环,不需要使用"=="

char *elementStart = memoryBlock + elementNr*fixedElementSize;
char special = 0;
for ( size_t curByteNr=0; curByteNr<fixedElementSize; ++curByteNr )
{
  special |= (*(elementStart+curByteNr));
}

,也可以使用long来获得更快的速度

char *elementStart = memoryBlock + elementNr*fixedElementSize;
long special = 0;
for ( size_t curByteNr=0; curByteNr<fixedElementSize; curByteNr += sizeof(long) )
{
   special |= *(long*)(elementStart+curByteNr);
}

警告:上面的代码没有被测试。请先测试它,以便sizeof和cast操作符工作

我已经测试了这里提出的一些解决方案,并检查了memcmp源代码,该源代码未针对OP需求进行优化,因为它有额外的要求来执行排序,导致它逐个比较unsigned char

在下文中,我提出了一个优化的函数check_memory_zeroed,它对可用的最大对齐int执行大部分检查,使其可移植,并将其与本线程中提出的其他解决方案进行比较。执行时间测量并打印结果。

结果表明,所提出的解决方案比wallyk的明显的可移植高效方法好近两倍,且不需要创建额外的数组,比char by char比较或mihaif的移位数组比wallyk的节省内存的方法好6倍。

我也测试了我的解决方案,没有对齐单词check_memory_zeroed_bigestint_not_aligned,令人惊讶的是,它的性能甚至更好。如果有人有解释,欢迎。

下面是在1Gb表上进行功能和性能测试的代码(建议的优化函数是第一个:check_memory_zeroed):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#include <time.h>
#define BIG_TAB_SIZE 1000000000
typedef intmax_t biggestint;
int check_memory_zeroed (void* ptr, size_t size)
{
    if (ptr == NULL) return -1;
    int bis = sizeof(biggestint);
    char* pc = (char*) ptr;
    biggestint* pbi0 = (biggestint*) pc;
    if ((size_t) pc % bis) /* is aligned ? */
        pbi0 = (biggestint*) (pc + (bis - ((size_t) pc % bis))); /* minimal pointer larger than ptr but aligned */
    assert ((size_t) pbi0 % bis == 0); /* check that pbi0 is aligned */
    for (char* p = pc; p < (char*) pbi0; p++)
        if(*p) return 0; /* check beginning of non aligned array */
    biggestint* pbi = pbi0;
    biggestint* pbiUpper = ((biggestint*) (pc + size)) - 1;
    for (;pbi <= pbiUpper; pbi++)
        if(*pbi) return 0; /* check with the biggest int available most of the array : its aligned part */
    for (char* p = (char*) pbi; p < pc + size; p++)
        if(*p) return 0; /* check end of non aligned array */
    return 1;
}
int check_memory_zeroed_bigestint_not_aligned (void* ptr, size_t size)
{
    if (ptr == NULL) return -1;
    biggestint* pbi = (biggestint*) ptr;
    biggestint* pbiUpper = ((biggestint*) (((char*) ptr) + size)) - 1;
    for (;pbi <= pbiUpper; pbi++)
        if(*pbi) return 0; /* check with the biggest int available most of the array, but without aligning it */
    for (char* p = (char*) pbi; p < ((char*) ptr) + size; p++)
        if(*p) return 0; /* check end of non aligned array */
    return 1;
}
int check_memory_zeroed_by_char (void* ptr, size_t size)
{
    if (ptr == NULL) return -1;
    for (char* p = (char*) ptr; p < ((char*) ptr) + size; p++)
        if(*p) return 0;
    return 1;
}
/* variant of wallyk solution */
int check_memory_zeroed_by_memcmp_and_testblock (void* ptr, size_t size)
{
    void* testblock = malloc(size);
    if (ptr == NULL || testblock == NULL) return -1;
    memset (testblock, 0, sizeof(testblock));
    int res = ! memcmp (testblock, ptr, size);
    free (testblock);
    return res;
}
/* variant of mihaif solution */
int check_memory_zeroed_by_memcmp_with_shifted_array (void* ptr, size_t size)
{
    if (ptr == NULL) return -1;
    char* pc = (char*) ptr;
    return (*pc) || memcmp(pc, pc + 1, size - 1);
}
int test() {
    /* check_memory_zeroed (void* ptr, size_t size) */
    char tab[16];
    for (int i = 0; i < 8; i++)
        for (int j = 0; j < 8; j++) {
            for (int k = 0; k < 16; k++) tab[k] = (k >= i && k < 16 - j) ? 0 : 100 + k;
            assert(check_memory_zeroed(tab + i, 16 - j - i));
            if (i > 0) assert(tab[i-1] == 100 + i - 1);
            if (j > 0) assert(tab[16 - j] == 100 + 16 - j);
            for (int k = i; k < 16 - j; k++) {
                tab[k] = 200+k;
                assert(check_memory_zeroed(tab + i, 16 - j - i) == 0);
                tab[k] = 0;
            }
        }
    char* bigtab = malloc(BIG_TAB_SIZE);
    clock_t t = clock();
    printf ("Comparison of different solutions execution time for checking an array has all its values nulln");
    assert(check_memory_zeroed(bigtab, BIG_TAB_SIZE) != -1);
    t = clock() - t;
    printf ("check_memory_zeroed optimized : %f secondsn",((float)t)/CLOCKS_PER_SEC);
    assert(check_memory_zeroed_bigestint_not_aligned(bigtab, BIG_TAB_SIZE) != -1);
    t = clock() - t;
    printf ("check_memory_zeroed_bigestint_not_aligned : %f secondsn",((float)t)/CLOCKS_PER_SEC);
    assert(check_memory_zeroed_by_char(bigtab, BIG_TAB_SIZE) != -1);
    t = clock() - t;
    printf ("check_memory_zeroed_by_char : %f secondsn",((float)t)/CLOCKS_PER_SEC);
    assert(check_memory_zeroed_by_memcmp_and_testblock(bigtab, BIG_TAB_SIZE) != -1);
    t = clock() - t;
    printf ("check_memory_zeroed_by_memcmp_and_testblock by wallyk : %f secondsn",((float)t)/CLOCKS_PER_SEC);
    assert(check_memory_zeroed_by_memcmp_with_shifted_array(bigtab, BIG_TAB_SIZE) != -1);
    t = clock() - t;
    printf ("check_memory_zeroed_by_memcmp_with_shifted_array by mihaif : %f secondsn",((float)t)/CLOCKS_PER_SEC);
    free (bigtab);
    return 0;
}
int main(void) {
    printf("Size of intmax_t = %lun", sizeof(intmax_t));
    test();
    return 0;
}

和不同解决方案的比较结果检查数组的所有值是否为null的执行时间:

  • intmax_t = 8的大小
  • check_memory_zeroed optimized: 0.331238 seconds
  • check_memory_zeroed_bigestint_not_aligned: 0.260504 seconds
  • check_memory_zeroed_by_char: 1.958392秒
  • check_memory_zeroed_by_memcmp_and_testblock by wallyk: 0.503189秒
  • check_memory_zeroed_by_memcmp_with_shifted_array by mihaif: 2.012257秒

不可能同时检查所有100个字节。因此,在任何情况下,您(或任何实用程序函数)都必须遍历数据。但是,除了让步长大于1字节之外,您还可以进行更多的优化:例如,您可以在找到非零值时立即执行break。我知道,时间复杂度仍然是O(n)

我想不起一个标准库函数可以为您做到这一点。如果你不确定这会导致任何性能问题,我只是使用循环,也许用int*代替char*,正如已经建议的。

如果你必须优化,你可以展开循环:

bool allZeroes(char* buffer)
{
    int* p = (int*)buffer;   // you better make sure your block starts on int boundary
    int acc = *p;
    acc |= *++p;
    acc |= *++p;
    ...
    acc |= *++p;    // as many times as needed
    return acc == 0;
}

如果它的大小不是sizeof(int)的倍数,您可能需要为缓冲区的末尾添加特殊处理,但是分配一个稍大的块并将一些填充字节设置为0可能会更有效。

如果你的块很大,你可以把它们当作一个小块的序列,并对它们进行循环,对每个小块使用上面的代码。

我很想知道这个解决方案与std::upper_bound(begin,end,0)memcmp相比如何。

编辑

快速检查了自己开发的实现与memcmp的比较,使用VS2010。

总之:

1)在调试模式下,自生长的速度是memcmp

的两倍

2)在完全优化的版本中,memcmp在以非0开头的块上有一个边。随着零填充的序文长度的增加,它开始丢失,然后不知怎的神奇地变得几乎和本地一样快,大约只慢10%。

因此,根据您的数据模式和优化的需要/愿望,您可以通过推出自己的方法获得一些额外的性能,但是memcmp是一个相当合理的解决方案。

将把代码和结果放在github上,以防你可以使用它们。

下面的代码将遍历结构体的内存。唯一的缺点是按字节检查。

#include <iostream>
struct Data { int i; bool b; };
template<typename T>
bool IsAllZero(T const& data)
{
    auto pStart = reinterpret_cast<const char*>(&data);
    for (auto pData = pStart; pData < pStart+sizeof(T); ++pData)
    {
        if (*pData)
            return false;
    }
    return true;
}
int main()
{
    Data data1;// = {0}; // will most probably have some content
    Data data2 = {0}; // all zeroes
    std::cout << "data1: " << IsAllZero(data1) << "ndata2: " << IsEmptyStruct(data2);
    return 0;
};

如何使用long int和二进制or运算符

unsigned long long int *start, *current, *end, value = 0;
// set start,end
for(current = start; current!=end; current++) {
value |= *current;
}
bool AllZeros = !value;

如果你只是想决定一个元素是否都是0,你可以create a 100byte element with all 1s。现在,当您想要检查一个元素是否全部为0时,只需binary AND (&)元素的内容和您创建的元素(全部为1)。现在你选中的if the result of binary AND is zero元素全是0否则就不全是0

创建一个全是15的100字节的元素似乎很昂贵,但如果你有大量的元素要检查,那么实际上更好

您可以创建100字节的元素,所有的1s作为void *elem; elem=malloc(100);现在将所有位设置为1(使用~(elem&0))