Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compare structs in C using memcmp() and pointer arithmetic

Tags:

c

gcc

struct

memcmp

I know that memcmp() cannot be used to compare structs that have not been memset() to 0 because of uninitialized padding. However, in my program I have a struct with a few different types at the start, then several dozen of the same type until the end of the struct. My thought was to manually compare the first few types, then use a memcmp() on the remaining contiguous memory block of same typed members.

My question is, what does the C standard guarantee about structure padding? Can I reliably achieve this on any or all compilers? Does the C standard allow struct padding to be inserted between same type members?

I have implemented my proposed solution, and it seems to work exactly as intended with gcc:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct foo
{
    char a;
    void *b;
    int c;
    int d;
    int e;
    int f;
};

static void create_struct(struct foo *p)
{
    p->a = 'a';
    p->b = NULL;
    p->c = 1;
    p->d = 2;
    p->e = 3;
    p->f = 4;
}

static int compare(struct foo *p1, struct foo *p2)
{
    if (p1->a != p2->a)
        return 1;

    if (p1->b != p2->b)
        return 1;

    return
        /* Note the typecasts to char * so we don't get a size in ints. */
        memcmp(
            /* A pointer to the start of the same type members. */
            &(p1->c),
            &(p2->c),
            /* A pointer to the start of the last element to be compared. */
            (char *)&(p2->f)
            /* Plus its size to compare until the end of the last element. */
            +sizeof(p2->f)
            /* Minus the first element, so only c..f are compared. */
            -(char *)&(p2->c)
        ) != 0;
}

int main(int argc, char **argv)
{
    struct foo *p1, *p2;
    int ret;

    /* The loop is to ensure there isn't a fluke with uninitialized padding
     * being the same.
     */
    do
    {
        p1 = malloc(sizeof(struct foo));
        p2 = malloc(sizeof(struct foo));

        create_struct(p1);
        create_struct(p2);

        ret = compare(p1, p2);

        free(p1);
        free(p2);

        if (ret)
            puts("no match");
        else
            puts("match");
    }
    while (!ret);

    return 0;
}
like image 707
John Avatar asked Oct 03 '22 03:10

John


1 Answers

There is no guarantee of this in the C standard. From a practical standpoint it's true as part of the ABI for every current C implementation, and there seems to be no purpose in adding padding (e.g. it could not be used for checking against buffer overflows, since a conforming program is permitted to write to the padding). But strictly speaking it's not "portable".

like image 176
R.. GitHub STOP HELPING ICE Avatar answered Oct 11 '22 13:10

R.. GitHub STOP HELPING ICE