cmacrosstatic-assert

How to static_assert in an expression out of function bodies in C


I am writing a macro PAGE_ALIGN in C as follows.

#include <stdio.h>

#define PAGE_ALIGN(x) ((x) & 0xfffff000U)

int main() {
    printf("0x%08x\n", PAGE_ALIGN(0x12345678U));
}

Now I want to make sure that the user is passing in the correct type to PAGE_ALIGN. I use _Static_assert() to perform the assertion, and things work well.

#define PAGE_ALIGN(x) (({_Static_assert(sizeof(x) == 4); x;}) & 0xfffff000U)

However, some of my code does not use PAGE_ALIGN in functions. For example, when defining a global variable:

char a[PAGE_ALIGN(0x12345678U)];

Then I get the compile error

a.c:3:24: error: braced-group within expression allowed only inside a function
    3 | #define PAGE_ALIGN(x) (({_Static_assert(sizeof(x) == 4); x;}) & 0xfffff000U)
      |                        ^
a.c:5:8: note: in expansion of macro 'PAGE_ALIGN'
    5 | char a[PAGE_ALIGN(0x12345678U)];
      |        ^~~~~~~~~~

Is there a way to define PAGE_ALIGN such that the macro works outside of a function?

Complete a.c:

#include <stdio.h>

#define PAGE_ALIGN(x) (({_Static_assert(sizeof(x) == 4); x;}) & 0xfffff000U)

char a[PAGE_ALIGN(0x12345678U)];

int main() {
    printf("0x%08x\n", PAGE_ALIGN(0x12345678U));
}

Update: here is the motivation for this question.

I am writing an OS that deals with physical address and virtual address in 32-bit and 64-bit mode. Virtual addresses are 32 bits in 32-bit mode and 64 bits in 64-bit mode. So unsigned long is used for virtual addresses. Physical addresses are always 64 bits, so unsigned long long is used. I am writing a header file that distinguishes these different types

For example, page size macros are:

#define VA_PAGE_SIZE_4K 0x1000UL  // VA for virtual address
#define PA_PAGE_SIZE_4K 0x1000ULL // PA for physical address

Then I define macros for aligning up:

#define VA_PAGE_ALIGN_UP_4K(x) (((x) + VA_PAGE_SIZE_4K - 1) & ~(VA_PAGE_SIZE_4K - 1))
#define PA_PAGE_ALIGN_UP_4K(x) (((x) + PA_PAGE_SIZE_4K - 1) & ~(PA_PAGE_SIZE_4K - 1))

Please imagine that there are other macros, such as VA_PAGE_ALIGN_UP_2M, VA_PAGE_ALIGN_UP_1G, ...

My OS has a fixed supported physical address size (say 4 GiB) and I want to need an identity map page table. So I can use the alignment macros to compute how many page table entries I need to support. Of course, there are page directories etc. due to multi-level paging.

// maximum physical memory, 4G
#define MAX_PHYS_MEM 0x100000000ULL

// number of page table entries
#define PAGE_TABLE_NELEMS (PA_PAGE_ALIGN_UP_4K(MAX_PHYS_MEM) / PA_PAGE_SIZE_4K)

// Define page table (global variable)
unsigned long page_table[PAGE_TABLE_NELEMS];

// number of page directory entries
#define PAGE_DIRECTORY_NELEMS (PA_PAGE_ALIGN_UP_2M(MAX_PHYS_MEM) / PA_PAGE_SIZE_2M)

// Define page directory (global variable)
unsigned long page_directory[PAGE_DIRECTORY_NELEMS];

// ...

However, in other code I want to make sure that PA_PAGE_ALIGN_UP_4K and VA_PAGE_ALIGN_UP_4K are not mixed. That is, PA_PAGE_ALIGN_UP_4K only used on unsigned long long and VA_PAGE_ALIGN_UP_4K only used on unsigned long. So I want to add a static assert in those macros. But adding a static assert will cause compile errors in page_table and page_directory above.


Solution

  • @Tavian Barnes's comment solves my problem. My updated program looks like:

    #include <stdio.h>
    
    #define PAGE_ALIGN(x) (sizeof(struct { _Static_assert(sizeof(x) == 4); int dummy; }) * 0 + ((x) & 0xfffff000U))
    
    char a[PAGE_ALIGN(0x12345678U)];
    
    int main() {
        printf("0x%08x\n", PAGE_ALIGN(0x12345678U));
    }