2

Let's assume that I declared a C struct for holding configuration information, which has lots of fields(~30), e.g.:

struct config { int a; int b; int c; ... char *str1; char *str2; ... }; 

What is the best way to compare two structs without simply enumerating and comparing each element? Of course I could use the following code but my question is, is there an easier way to achieve this?

static int cmpInstances(struct config *l, struct config *r) { return ( l->a == r->a && l->b == r->b && l->c == r->c && !strcmp(l->str1, r->str1) && !strcmp(l->str2, r->str2) ); } 

Is there some macro that could do that? For example: cmpInstancesViaMacro(a,b,c) would expand into

 l->a == r->a && l->b == r->b && l->c == r->c 
9
  • 3
    You could use memcmp, but it will also compare the padding bytes. Commented Jan 14, 2022 at 17:48
  • 7
    @EugeneSh. And it also won't compare the strings pointed to by str1, str2, etc, but just the pointer values Commented Jan 14, 2022 at 17:48
  • 1
    @Kevin That's right, it will be a "shallow" comparison, and probably the pointer values are not something the OP is interested in. Commented Jan 14, 2022 at 17:49
  • 1
    Unfortunately, there's nothing simpler if you need to compare the targets of pointers. Commented Jan 14, 2022 at 17:49
  • 4
    @jtxkopt They would. But the same data might reside in different regions. The OP seem to be interested in comparing the data. Commented Jan 14, 2022 at 17:58

4 Answers 4

3

A generalized variant of so-called X Macros could be used.

In the example of the question, it would look like this:

#define CONFIG_FIELDS \ X_INT(a) \ X_INT(b) \ X_INT(c) \ X_STRING(str1) \ X_STRING(str2) struct config { #define X_INT(NAME) int NAME; #define X_STRING(NAME) char *NAME; CONFIG_FIELDS #undef X_INT #undef X_STRING }; static int cmpInstances(struct config *l, struct config *r) { #define X_INT(NAME) l->NAME == r->NAME && #define X_STRING(NAME) strcmp(l->NAME, r->NAME) == 0 && return CONFIG_FIELDS 1; #undef X_INT #undef X_STRING } 

With this solution, only the CONFIG_FIELDS macro needs to be changed when the structure is changed as long as there is no change to the set of types used. The structure and function would automatically be kept synchronized.

If a new type is introduced, the corresponding X Macros must be defined similar to X_INT and X_STRING above.

Sign up to request clarification or add additional context in comments.

2 Comments

So if you had a large vocabulary of these, the #define sections get replaced by #include "x-decl.h" ... #include "x-undef.h", ... #include "x-compare.h", .. #include "x-undef.h" and so on.
@Kaz: I guess you could simply define the X_INT and X_STRING macros outside the set of cmpInstances functions and define them together.
2

Perhaps some macros will help us reduce the lines of code and effort needed:

#define CMP(name) l->name == r->name && #define SCMP(name) !strcmp(l->name, r->name) && struct config { int a; int b; int c; char *str1; char *str2; }; static int cmpInstances(struct config *l, struct config *r) { return CMP(a) CMP(b) CMP(c) SCMP(str1) SCMP(str2) 1; } #undef CMP #undef SCMP 

Here we define some macros that take only the name of the variable and compare them. One is written for integer comparison (CMP) and the other is written for string comparison (SCMP). Then you can use these macros to compare your data and undef them when you are done.

Thanks to chqrlie for adding 1 part.

1 Comment

You should provide a minimum of explanations
2

What is the best way to compare two structs without simply enumerating and comparing each element?

The presented way is the best. Except, the pointers should point to const - nothing is getting modified.

is there an easier way to achieve this?

The presented way is the easiest way to achieve it, it's also the most readable and self-explanatory, easy to refactor and reason about. Any other will add to complexity and result in maintenance burden.

I find strcmp(...) == 0 clearer then !strcmp(...).

1 Comment

For small structures, I agree that plain C code is the best option, but if the structure becomes big it will get harder to check that all elements have been remembered in the compare function. The approach in my answer removes that burden, so if the structure is sufficiently large, I would personally prefer that.
1

If you're the author of the struct, you can lay it out so there's no padding, _Static_assert that there's no padding (sizeof(whole+struct) == sum of component sizes) and then simply use one memcmp call if all the data is in the struct.

If the struct has string pointers (and you can't guarantee that a string with particular contents is stored only in one place), you'll need to do those separately.

You'll get the tightest code if you put all of them next to each other, union-them with a corresponding char*[] array, and then loop over the corresponding arrays calling strcmp on each pair and combine the result (if all were equal) with memcpy for the rest.

Something like:

#include <string.h> struct config { int m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15,m16; union { struct { char *s1,*s2,*s3,*s4,*s5,*s6,*s7,*s8; }; char *strings[8]; }; }; _Static_assert(sizeof(struct config) == sizeof(int)*16+sizeof(char*)*8,""); int strings_equal(char *const A[], char *const B[], size_t Count){ for(;Count;Count--) if(0!=strcmp(*A++,*B++)) return 0; return 1; } int configs_equal(struct config const *A, struct config const *B){ return 0==memcmp(A,B,sizeof(int)*16) && strings_equal(A->strings,B->strings,sizeof(A->strings)/sizeof(A->strings[0])); } 

2 Comments

memcmp the struct won't compare correctly the strings
@bolov Fixed that.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.