1

I would like to design c structure via padding by given offsets in less time and easy to modifications.

Lets take an example,

Assume my structure is the following:

#pragma pack(push, 1)
typedef struct 
{
char dummy[15]; // offset 0
unsigned int field1; // offset 15
char dummy[45]; // offset 19
unsigned int field2; // offset 64
char dummy[25]; // offset 68
unsigned int field3; // offset 93
}
#pragma pack(pop)

My idea is to find the way to give to developer (to me) a way to write structure easily. For example, given offsets want to declare structure like this:

#define SET_FIELD ???

#pragma pack(push, 1)
typedef struct 
{
SET_FIELD(unsigned int, field1, 15); 
SET_FIELD(unsigned int, field2, 64);
SET_FIELD(unsigned int, field3, 93);
}
#pragma pack(pop)

Of course, the "SET_FIELD" definition will do padding automatically Anyone have an idea Thanks,

sivandahan
  • 71
  • 5
  • C and C++ are different languages. Don't use the C++ tag, if you are asking a question about C. – lulle2007200 Aug 18 '21 at 12:44
  • 1
    How does 15 make any sense `char dummy[15]; // offset 0 unsigned int field1; // offset 15`? The compiler will have to insert padding there. So why 15 instead of 16? Do you actually want the int to be misaligned for some strange reason? – Lundin Aug 18 '21 at 12:46
  • Also, the compiler might (and most certainly will in this case) insert additional padding, making your explicit padding bytes effectively useless. if you do what you suggested, you have to mark the struct as packed. This looks like an XY problem. Why do you want to have members at a specific offset in the struct in the first place? – lulle2007200 Aug 18 '21 at 12:47
  • 1
    Overall, what is the actual problem you are trying to solve? Your struct declaration isn't "easy" nor does it make any sense. – Lundin Aug 18 '21 at 12:48
  • It's possible with boost-preprocessor (or a similar library, or by writing boilerplate macros by hand), but it's compilcated. – HolyBlackCat Aug 18 '21 at 12:48
  • Unless you are dealing with some low level stuff you shouldn't worry about offsets... – Support Ukraine Aug 18 '21 at 12:48
  • and multiple members with the same name is bad as well – Support Ukraine Aug 18 '21 at 12:51
  • For who one give a note about the alignment, It's fixed by declaration of "pragma pack". I'm sorry about my wrong, but the most important is the main question. anyone I any idea to solve the problem ? Thanks ! – sivandahan Aug 18 '21 at 12:57
  • No, pragma pack does not let you access an int misaligned. Whether that is at all possible or not depends on the CPU. For the majority of 32 bit or larger CPUs out there, it's a bad idea. – Lundin Aug 18 '21 at 13:29
  • "any idea to solve the problem" What _is_ the problem? That is, what actual problem is this strange struct supposed to solve? – Lundin Aug 18 '21 at 13:29
  • @Lundin: When structures are packed, the compiler generates whatever instructions are necessary to access an unaligned structure member. – Eric Postpischil Aug 18 '21 at 15:59
  • @EricPostpischil If such instructions are available. There are ISA that don't have them. Some MIPS I believe, likely others too. At any rate, misalignment likely leads to slower code even on ISA that can do misaligned access. – Lundin Aug 18 '21 at 18:44
  • @Lundin: All processors have instructions that can load unaligned structure members. That does not need to be an “unaligned load word” instruction. It can be “load byte, shift, or, load byte, shift, or, load byte, shift, or, load byte, or”. The compiler generates whatever instructions are necessary. – Eric Postpischil Aug 18 '21 at 20:04
  • What you're doing is a little different, but closely related to [Is it possible to dynamically define a structure type in C?](https://stackoverflow.com/q/6187908/15168) – Jonathan Leffler Aug 18 '21 at 23:29

4 Answers4

3

This seems like a bad idea, but you can accomplish it by abusing a union and anonymous structure members:

#define SET_FIELD(Type, Name, Offset) \
    struct { unsigned char Padding##Offset[Offset]; Type Name; }

#pragma pack(push, 1)
typedef union
{
    SET_FIELD(unsigned int, field1, 15); 
    SET_FIELD(unsigned int, field2, 64);
    SET_FIELD(unsigned int, field3, 93);
} ManualStructure;
#pragma pack(pop)


#include <stddef.h>
#include <stdio.h>


int main(void)
{
    printf("field1 is at offset %zu.\n", offsetof(ManualStructure, field1));
    printf("field2 is at offset %zu.\n", offsetof(ManualStructure, field2));
    printf("field3 is at offset %zu.\n", offsetof(ManualStructure, field3));
}

The output of the above is:

field1 is at offset 15.
field2 is at offset 64.
field3 is at offset 93.

The fact that the array used for padding is named Padding##Offset will also alert you to erroneous uses of the same offset, since that will result in two members with the same name. However, it will not warn you of partial overlaps.

You can also do it with GCC and Clang’s attribute feature instead of a #pragma:

#define SET_FIELD(Type, Name, Offset) \
    struct __attribute__((__packed__)) { unsigned char Padding##Offset[Offset]; Type Name; }

typedef union
{
    SET_FIELD(unsigned int, field1, 15); 
    SET_FIELD(unsigned int, field2, 64);
    SET_FIELD(unsigned int, field3, 93);
} ManualStructure;

A problem is that writing to any member of a union is allowed by the C standard to affect bytes that do not correspond to that member but that do correspond to others. For example, given ManualStructure x, the assignment x.field1 = 3; is allowed to alter the bytes of x.field3, since those bytes do not correspond to the bytes of the structure that contains x.field1. You might workaround this by including an additional member in each anonymous structure that pads its bytes out to the full length of the ManualStructure. Then, whenever you write to a field, you are writing to a member whose bytes occupy the entire union, so there are none that do not correspond to that member. However, to do this, you would have to know the total size of the structure in advance or at least be able to select some bound for it.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

Premise: doing this is probably counter-productive, and may be solved in a different way, if you're willing to tell us why you need to specify members at specific offsets.

Actual answer: if I had your same problem, I'd solve it using an external scripting language in order to generate the struct definition before compiling the code.

For instance, you could type inside your .c or .h file a special comment like:

///DEFSTRUCT: mystruct (unsigned int, field1, 15) (unsigned int, field2, 25)

And then I'd write a quick script in JavaScript/Python/Perl/whatever that simply reads your .c or .h file, finds the lines that start with ///DEFSTRUCT: and then will replace them with the correct packed struct definition.

Doing this without an external scripting language would a great PITA, in my opinion. Plus, debugging a JavaScript/Python program is way easier than debugging the C preprocessor.

Luca Polito
  • 2,387
  • 14
  • 20
0

This is doable, but you need to use a single macro to define the whole struct.

The problem with this implementation is that it produces a zero-size padding array at the end (can be eliminated with more complex macros) and zero-size arrays between adjacent fields (can't be helped). I don't think standard C allows zero-size arrays, but some compilers accept them.

run on gcc.godbolt.org

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x##y

#define MAKE_STRUCT(seq) char padding_first[ END( MAKE_STRUCT_LOOP_A seq ) * 0 ];
#define MAKE_STRUCT_LOOP_A(...) MAKE_STRUCT_LOOP_BODY(__VA_ARGS__) MAKE_STRUCT_LOOP_B
#define MAKE_STRUCT_LOOP_B(...) MAKE_STRUCT_LOOP_BODY(__VA_ARGS__) MAKE_STRUCT_LOOP_A
#define MAKE_STRUCT_LOOP_A_END
#define MAKE_STRUCT_LOOP_B_END
#define MAKE_STRUCT_LOOP_BODY(type, name, offset) \
    + (offset)]; \
    type name; \
    char CAT(padding_after_,name)[-(offset + sizeof(type))

struct A
{
    MAKE_STRUCT(
        (unsigned int, field1, 15)
        (unsigned int, field2, 64)
        (unsigned int, field3, 93)
    )
};

This expands to:

struct A
{
    char padding_first[ + (15)]; // 15
    unsigned int field1;
    char padding_after_field1[-(15 + sizeof(unsigned int)) + (64)]; // 45
    unsigned int field2;
    char padding_after_field2[-(64 + sizeof(unsigned int)) + (93)]; // 25
    unsigned int field3;
    char padding_after_field3[-(93 + sizeof(unsigned int)) * 0 ]; // 0
};
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
-1

It's not doable using a single macro that you use N times (here N being 3).

Consider this

SET_FIELD(unsigned int, field3, 93);

that you want to turn into

char dummy[25];
unsigned int field3;

Now how would the macro get a padding size of 25 (i.e. char dummy[25];) from the number 93? It can't... That would require knowledge of all previous use of the SET_FIELD macro.

If you really want something like that you can write a separate program that can parse your desired struct-definition syntax and generate a normal include file (.h file) for your C project.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • *"would require knowledge of all previous use of the SET_FIELD"* Exactly, so you would use a single macro to define all fields (using one of the looping macros provided by boost-preprocessor, for example). – HolyBlackCat Aug 18 '21 at 14:40
  • @HolyBlackCat I'm only saying that it's not possible using C macros. That's all. – Support Ukraine Aug 18 '21 at 16:39
  • And I'm telling you it's possible. Tricky? Yes. Impossible? No. – HolyBlackCat Aug 18 '21 at 17:03
  • @HolyBlackCat Then please show how you would do it with C macros. After all that's what OP is asking. So far we have an upvoted non-standard answer and a downvoted IMO correct answer. If you can provide a solution with standard C, I'm happy to delete my answer. – Support Ukraine Aug 18 '21 at 17:17
  • [See my answer](https://stackoverflow.com/a/68837165/2752075). I didn't want to dig into Boost.Preprocessor at first, but accepted this as a challenge. – HolyBlackCat Aug 18 '21 at 17:50
  • @HolyBlackCat Well, your answer is kind of cool but... 1) It actually proves my point by requiring that all field information are provided via a single macro. That's exactly what my answer say... it can't be done using the the same macro 3 (or N) times. 2) As you say yourself... the solution violates the standard by having zero-sized arrays. So I find your answer kind of cool but I'll stick to my answer: It can't be done. – Support Ukraine Aug 19 '21 at 07:22
  • *"I'll stick to my answer"* Fair. *"exactly what my answer say... it can't be done using the the same macro 3 (or N) times"* Nope, your answer says *"not doable using macros"*, which, at least to me, implies every possible macro notation. – HolyBlackCat Aug 19 '21 at 07:55
  • 1
    @HolyBlackCat Fair... I changed the wording as I agree that it could be misunderstood. – Support Ukraine Aug 19 '21 at 08:16