The alignment of dynamically allocating memory

Check Notes from max_align_t:

Pointers returned by allocation functions such as malloc are suitably aligned for any object, which means they are aligned at least as strictly as max_align_t.

It means the memory allocated dynamically is guaranteed to alignof(max_align_t) bytes aligned.

Check Notes from aligned_alloc:

Passing a size which is not an integral multiple of alignment or a alignment which is not valid or not supported by the implementation causes the function to fail and return a null pointer (C11, as published, specified undefined behavior in this case, this was corrected by DR 460).

It means the alignment for aligned_alloc is implementation dependent.

Write a simple program to test aligned_alloc behavior in macOS and Linux (X86_64):

$ cat align.c
#include <stdalign.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("alignof(max_align_t)=%zu\n\n", alignof(max_align_t));

    size_t size = 1024;
    size_t align[] = {1, 2, 4, 8, 16, 32, 64};
    for (size_t i = 0; i < sizeof(align) / sizeof(align[0]); i++)
    {
        void *p = aligned_alloc(align[i], size);
        printf("align=%zu, pointer is %p\n", align[i], p);
        free(p);
    }
}

Build and run it in macOS:

$ cc align.c -o align
$ ./align
alignof(max_align_t)=16

align=1, pointer is 0x0
align=2, pointer is 0x0
align=4, pointer is 0x0
align=8, pointer is 0x7fbd48801600
align=16, pointer is 0x7fbd48801600
align=32, pointer is 0x7fbd48801600
align=64, pointer is 0x7fbd48801600

In Linux (X86_64):

$ cc align.c -o align
$ ./align
alignof(max_align_t)=16

align=1, pointer is 0x5645aec676b0
align=2, pointer is 0x5645aec676b0
align=4, pointer is 0x5645aec676b0
align=8, pointer is 0x5645aec676b0
align=16, pointer is 0x5645aec676b0
align=32, pointer is 0x5645aec67ac0
align=64, pointer is 0x5645aec67f40

Both macOS and Linux (X86_64) have the same alignment of allocating memory from free storage: 16 bytes. macOS requires the alignment of aligned_alloc is at least 8 bytes; whilst Linux (X86_64) doesn’t have this requirement.

P.S., the code can be downloaded here.

Use alignof in gcc

alignof is defined in stdalign.h for gcc:

$ cat /usr/lib/gcc/x86_64-unknown-linux-gnu/9.1.0/include/stdalign.h
......
#ifndef _STDALIGN_H
#define _STDALIGN_H

#ifndef __cplusplus

#define alignas _Alignas
#define alignof _Alignof

#define __alignas_is_defined 1
#define __alignof_is_defined 1

#endif

#endif  /* stdalign.h */

To use alignofstdalign.h must be included, otherwise following errors will be reported:

......
warning: implicit declaration of function 'alignof' [-Wimplicit-function-declaration]
    3 |  return alignof(int);
      |         ^~~~~~~
 error: expected expression before

It is no need to include any header files to use _Alignof and __alignof__.

Empty struct in C

Empty struct in C is undefined behaviour (refer C17 spec, section 6.7.2.1):

If the struct-declaration-list does not contain any named members, either directly or via an anonymous structure or anonymous union, the behavior is undefined.

On my Linux, I use both gcc (v9.1.0) and clang (v8.0.0) to compile following code:

$ cat test.c
#include <stdio.h>

typedef struct A {} A;

int main()
{
    printf("%zu\n", sizeof(A));
}

No warnings, and both ouput:
0

Reference:
C empty struct — what does this mean/do?.

As a system programming language, C still deserves learning today

C is a system programming language which is old but with “bad” fame: undefined behavior, notorious memory related bugs, etc. Especially with Go and Rust having gone viral right now, C seems already forgotten by people. Nevertheless, IMHO, C is still a thing which is worth for you spending some time on it.

Whether you are are a C novice or a C veteran,I highly recommend you read Modern C if you haven’t read it before. Then you will find C also evolves stealthily and is not as primitive as you think. E.g., C11 has defined standard thread APIs like C++ has done, which makes C more like a “modern” language, not an outdated thing. You may get a new perspective of C from this book.

Regardless if you are a systems language programmer, DevOps, performance engineer or wear other hats, the more you know about the Operating System, the more you can do your job better. Take all prevailing Unix-like Operating Systems as an example, from kernel to command line tools, they are almost implemented in C. To study related source code can make you delve into Operating System internal deeper. E.g., I knew there is a taskset command which can bind specified process of a dedicated CPU, but I wanted to know the magic behind it, so I went through its code. Then I learned 2 things:
a) There is a “/proc/%pid/task” folder which records thread information of process;
b) taskset actually calls sched_setaffinity and sched_getaffinity APIs to attain its goal.

Last but not least, because C is so “low-level”, you can leverage it to write highly performant code to squeeze out CPU when performance is critical in some scenarios.

Although I have listed many reasons which I think you should learn C, you may have sufficient excuses to refuse to dabble into C too. It doesn’t matter. But if you want to give C a shot, firstly you should not be scared of it. There are many ways of writing wrong C code, but you only need to make sure what you write is correct and in defined behavior, that’s all about programming.