Last active
June 30, 2022 08:58
-
-
Save LabunskyA/4ac8bcf10c70e7223fe4a8c0b201f897 to your computer and use it in GitHub Desktop.
Save space on C structures padding (compiler-independent)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Use this macro to define variables to save space in your structures | |
* Imagine you have the following structure: | |
* struct A { | |
* void* my_ptr; // 8 bytes | |
* | |
* uint8_t my_flag :1; // merged with the next | |
* uint16_t my_uint :15; // 2 bytes total | |
* } | |
* | |
* The size of that structure is (16), because of the structure padding | |
* Compilers tend to pad members in such way that its total size will | |
* be divided by the size of the biggest primitive member (what?) and since | |
* (8 + 2 = 10) is not divided by (8), the structure is padded to the (16) | |
* | |
* You can use different compiler flags and stuff (like #pragma pack) to | |
* disable such padding, but that way the compiler can create less efficient | |
* code for pretty much every operation with every not aligned structure member | |
* | |
* Another way is to use fixed width byte array as a replacement for the | |
* structure member: | |
* | |
* struct B { | |
* char my_ptr[sizeof(void*)]; | |
* | |
* uint8_t my_flag :1; | |
* uint16_t my_uint :15; | |
* } | |
* | |
* Now the size of your structure is actually (10) bytes since it can be | |
* divided by the new biggest member size of (2). Memory representation | |
* of the structure will be (probably) the same as for the packed with pragma | |
* | |
* The only difference - you will need to use some hacks to access, write | |
* and copy value from such member. Since my_ptr is now a pointer to the | |
* char array, it can be cast for free to void* pointer array in which | |
* you can easily access the first and only element like this: | |
* | |
* ((void**) b.ptr)[0] | |
* | |
* The only problem - this access will require an additional (lea or some other) | |
* instruction (without compiler optimizations ofc) to find given variable | |
* effective address and copy it temporary to the CPU register. So, doing: | |
* | |
* void* some_ptr = ...; | |
* ((void**) b.ptr)[0] = some_ptr; | |
* | |
* will take 30% more instructions on x86_64 with gcc -O0 than | |
* a.ptr = some_ptr; | |
* | |
* On the other hand, memcpy call: | |
* memcpy(b.ptr, &some_ptr, sizeof(void*)); | |
* | |
* will transform in the exact same assembly code. Until you need to assign | |
* member to a constant value or pass its value to the function, you will have the | |
* exact same assembly code as a compilation result as the usual padded one | |
* | |
* | |
* This small macro library is allowing the kind of simple usage of a such technique | |
* You can transform only some members manually to reduce the performance penalty | |
* and structure size at the same time | |
*/ | |
#ifndef RAW_CTYPE | |
#define RAW_CTYPE | |
/* | |
* This macro will allow you to declare raw bytes structure member as following: | |
* | |
* struct my_struct { | |
* ... | |
* type_a member_a; | |
* raw_ctype_member(type_b, member_b); | |
* ... | |
* }; | |
* | |
* struct my_struct my_var; | |
* my_var.member_a; // type_a | |
* my_var.member_b; // char* | |
*/ | |
#define raw_ctype_member(type, var_name) char (var_name)[sizeof(type)] | |
/* | |
* Allowing access to your member as it was the same old typed one: | |
* raw_ctype(type_b, my_var.member_b); // type_b | |
*/ | |
#define raw_ctype(type, var) ((type*) (var))[0] | |
/* | |
* These macros are allowing you to copy value from and to "normal" variables of the | |
* same type to your raw member. Calling them will be faster (or the same, depending | |
* on compiler optimisations) on most platforms then accessing variable and assigning | |
* values via "=" | |
* | |
* Use like following: | |
* | |
* type_b some_other_b = ...; | |
* // some_other_b != raw_ctype(type_b, my_var.member_b) | |
* | |
* raw_ctype_copyfrom(my_var.member_b, some_other_b); | |
* // some_other_b == raw_ctype(type_b, my_var.member_b) | |
* | |
* type_b another_b; | |
* raw_ctype_copyto(another_b, my_var.member_b); | |
* // another_b == raw_ctype(type_b, my_var.member_b) | |
*/ | |
#define raw_ctype_copyfrom(var, src) memcpy((var), &(src), sizeof(src)) | |
#define raw_ctype_copyto(dest, var) memcpy(&(dest), var, sizeof(dest)) | |
/* | |
* You can always change macro names to be pretier | |
* then they are now. IDK. | |
*/ | |
#endif | |
/* | |
* Copyright (c) 2018 Labunsky Artem | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright notice, this | |
* list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright notice, | |
* this list of conditions and the following disclaimer in the documentation | |
* and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Идея в том, чтобы не использовать расширения компиляторов. С их помощью они (из тех, что вообще поддерживают такой атрибут) будут разворачивать код в другие, менее оптимальные наборы инструкций при работе со структурой (доступ, включая поля, присваивание и пр.), обещая нам лишь небольшой ее размер. Более того, это лишает гибкости в случаях, когда нас интересует свертка лишь определенных полей, и обеспечит головной болью при портировании кода между не то, что архитектурами, но даже между разными ОС :)