|
|
struct
s, padding is added automatically, so that each field is located at an offset (from the start of the struct
) that is an integer multiple of the field's size. For example, an int
field would located at an offset that is an integer multiple of 4 (assuming sizeof(int) == 4
), a double
field would be located at an offset that is an integer multiple of 8 (assuming sizeof(double) == 8
), and so on...struct
s. For example, GCC can use: __attribute__((__packed__))
__attribute__((aligned(N))
malloc()
and friends do not give you any guarantees that the start address of the allocated memory block is aligned to anything bigger than 1 – even though, in reality, malloc()
on most platforms has a certain minimum alignment. There are functions like _aligned_malloc()
(MSVC) or posix_memalign()
(Unix) to request a specific alignment.load
or store
instructions require that the source/target memory address is a multiple of N, when loading/storing an element of size N (in bytes). Often "unaligned" loads or stores are supported by the CPU, but they are less efficient, because each "unaligned" load or store effectively has to be translated into multiple "aligned" loads or stores!&sh = 00000069600FFB54 &a[0] = 00000069600FFB78 &a[1] = 00000069600FFB7C &a[2] = 00000069600FFB80 |
__attribute__((aligned(N)))
, then the compiler will use the "default" alignment for the target platform. This probably means that "global" or "local" variables will be aligned to an integer multiple of their type size.short
variable likely would be aligned to a multiple of 2, and that the int
array (and therefore each element of the array) likely would be aligned to a multiple of 4 – assuming that sizeof(short) == 2
and that sizeof(int) == 4
.
|
|
2, 0x564b6dbdf010 2, 0x564b6dbdf012 8, 0x564b6dbdf018 |
short
variables are aligned to a multiple of 2, whereas the long
variable is aligned to a multiple of 8. Because of this, the short
variables happen to be stored "back to back", but there needs to be some extra space/gap (4 bytes) before the long
variable.
|
|
2, 0x562f1ba32010 2, 0x562f1ba32020 8, 0x562f1ba32030 |
|
|
&sh = 00000093A7CFF994 &sh2 = 00000093A7CFF9B4 &d = 00000093A7CFF9D8 |
As far as dynamically allocated memory is concerned, I think malloc() and friends do not give you any guarantees that the start address of the allocated memory block is aligned to anything bigger than 1 – even though, in reality, malloc() on most platforms has a certain minimum alignment. There are functions like _aligned_malloc() (MSVC) or posix_memalign() (Unix) to request a specific alignment. |
"Immediate" operands are fixed values (constants) that are hard-coded into the program code. |
const int i {2};
?)
Could you write a simple example to figure out those fixed values. (Do you by any chance mean constant objects, e.g., const int i {2}; ?) |
i++
😏i
is the constant value 1, it can be hard-coded as "immediate" operand; otherwise we would have to first load the value "1" into a register and then add it to i
from there, which clearly is an unnecessary overhead)
Whenever the compiler can figure out the value of one of the operands as "fixed" (constant), at compile time, it can (and probably will) translate that value into an "immediate" operand. |
constexpr int i {3};
?
const
helps the compiler in optimizing, though.
|
|
short
here is better at least in terms or RAM usage. Although registers might be 16, 32, 64 or more bytes in size, the memory used for short
is 2 bytes while for int
it's 4 bytes, usually. Am I correct?
|
|
mov WORD PTR [rbp-0x2],0x4d2 mov DWORD PTR [rbp-0x8],0x4d2 mov WORD PTR [rbp-0x2],0x1b0 mov DWORD PTR [rbp-0x8],0x1b0 |
mov WORD PTR [rbp-0x2],0x4d2
stores the WORD (2 bytes) value 0x4D2 (decimal: 1234) at the memory location rbp-0x2, i.e. at the address of the local variable sh. Similarly, the instruction mov DWORD PTR [rbp-0x8],0x4d2
stores the DWORD (4 bytes) value 0x4D2 (decimal: 1234) at the memory location rbp-0x8, i.e. at the address of the local variable i.[rbp-0x2]
is a memory operand, whereas 0x4d2
is an immediate value. No registers involved.