简体   繁体   中英

Why can't you cast number into struct directly in C?

I have struct defined as:

typedef struct cr3_page_entry
{
   uint64_t ignored     : 2;   // ignored
   uint64_t pwt         : 1;   // page-level write-through; indirectly determines the memory type used to access the PML4 table during linear-address translation (see Section 4.9.2)
   uint64_t pcd         : 1;   // page-level cache disable; indirectly determines the memory type used to access the PML4 table during linear-address translation (see Section 4.9.2)
   uint64_t ignored2    : 7;   // ignored
   uint64_t address     : 53;  // rest is address up to MAXPHYADDR, then zeros
} cr3_page_entry_t;

which is a uint64_t, if you look at it (64bit sized struct). Now, I have an assembler function that returns such a number from it

extern uint64_t get_active_page();

however, I can't directly cast to it:

cr3_page_entry_t entry = (cr3_page_entry_t) get_active_page();

because I get an error:

memory/paging.c:19:2: error: conversion to non-scalar type requested
  cr3_page_entry_t entry = (cr3_page_entry_t) get_active_page();

can this be done without making a another stack variable for some pointer casting?

Define union with uint64_t and cr3_page_entry_t structure. Then assign to uint64_t member.

typedef union _cr3_u
{
   uint64_t i;
   cr3_page_entry_t str;
} cr3_u;

cr3_u my_data;

my_data.i = get_active_page();

The most portable solution is to memcpy() between the struct instance and a uint64_t variable. C99 and later (and many compilers older than that) allow type-punning through a union. In both cases, the exact layout of the bits in your bitfield is unspecified, so you should check that it's what you expect on your compiler. A less-safe solution is *(uint64_t*)(&struct_instance) . Be sure to confirm with static_assert() or #ifdef that sizeof(struct cr3_page_entry) == sizeof(uint64_t) . You should also check that the alignment of your fields is what you expect, if you care about portability between compilers.

One reason you can't do this is that the internal layout of bitfields isn't specified. The compiler does not have to pack them, or even align them at a valid address for a 64-bit integer. For example, on Sparc 64, a uint64_t must be aligned on an 8-byte boundary, so if you write a bunch of small bitfields and the compiler aligns them as if they were uint8_t , uint16_t or uint32_t , trying to load the struct as a uint64_t will crash the program with a bus error.

Could you avoid the cast altogether by declaring:

extern uint64_t get_active_page();

as:

extern cr3_page_entry_t get_active_page();

instead?

C doesn't allow you to cast to a struct type. In 99% of cases this wouldn't make sense, your case is a rare exception.

I'd recommend the following:

static inline struct cr3_page_entry make_cr3_page_entry(uint64_t x)
{
    struct cr3_page_entry ret;
    assert( sizeof ret == sizeof x );
    memcpy( &ret, &x, sizeof x );
    return ret;
}

Note: using an aliasing cast instead would violate the strict aliasing rule.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM