Handling Structs and Endianness

What's the best way to handle endianness when working with structs that contain multiple different-sized members?

When working with structs containing multiple members of different sizes, we need to handle each member's endianness individually. Here's a structured approach to handling this common scenario:

Basic Approach

First, let's look at a simple struct and how to properly serialize it:

#include <iostream>
#include "SDL.h"

struct PlayerData {
  Uint16 Level;
  Uint32 Experience;
  float Health;
};

void WritePlayerData(SDL_RWops* Handle,
                     const PlayerData& Data) {
  SDL_WriteLE16(Handle, Data.Level); 
  SDL_WriteLE32(Handle, Data.Experience); 
  SDL_WriteLE32(Handle, Data.Health);      
}

PlayerData ReadPlayerData(SDL_RWops* Handle) {
  PlayerData Data;
  Data.Level = SDL_ReadLE16(Handle); 
  Data.Experience = SDL_ReadLE32(Handle); 
  Data.Health = SDL_ReadLE32(Handle);      
  return Data;
}

int main() {
  // Create test data
  PlayerData TestData{
    .Level = 42,
    .Experience = 1000000,
    .Health = 98.5f
  };

  // Write to file
  SDL_RWops* WriteHandle{
    SDL_RWFromFile("player.dat", "wb")};
  if (WriteHandle) {
    WritePlayerData(WriteHandle, TestData);
    SDL_RWclose(WriteHandle);
  }

  // Read and verify
  SDL_RWops* ReadHandle{
    SDL_RWFromFile("player.dat", "rb")};
  if (ReadHandle) {
    PlayerData LoadedData{
      ReadPlayerData(ReadHandle)};
    SDL_RWclose(ReadHandle);

    std::cout << "Level: " << LoadedData.Level
      << "\nExp: " << LoadedData.Experience
      << "\nHealth: " << LoadedData.Health
      << '\n';
  }
}
Level: 42
Exp: 1000000
Health: 98.5

Advanced Techniques

For more complex structs, you might want to create a serialization helper class:

#include <iostream>
#include "SDL.h"

class BinaryWriter {
  SDL_RWops* Handle;

public:
  explicit
    BinaryWriter(SDL_RWops* Handle)
    : Handle{Handle} {}

  void Write(Uint16 Value) {
    SDL_WriteLE16(Handle, Value);
  }

  void Write(Uint32 Value) {
    SDL_WriteLE32(Handle, Value);
  }

  void Write(float Value) {
    SDL_WriteLE32(Handle, Value);
  }
};

struct PlayerData {
  Uint16 Level;
  Uint32 Experience;
  float Health;

  void Serialize(BinaryWriter& Writer) {
    Writer.Write(Level);
    Writer.Write(Experience);
    Writer.Write(Health);
  }
};

int main() {
  PlayerData Data{
    .Level = 42,
    .Experience = 1000000,
    .Health = 98.5f
  };

  SDL_RWops* Handle{
    SDL_RWFromFile("player.dat", "wb")};
  if (Handle) {
    BinaryWriter Writer{Handle};
    Data.Serialize(Writer); 
    SDL_RWclose(Handle);
  }
}

Key points to remember:

  • Always handle each member individually with the appropriate endianness function
  • Keep track of the order of serialization to match when deserializing
  • Consider padding and alignment in your structs
  • Use helper classes to make the code more maintainable
  • Document your serialization format for future maintenance

Byte Order and Endianness

Learn how to handle byte order in using SDL's endianness functions

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Detecting File Endianness
How can I detect if a binary file was written in big-endian or little-endian format if I don't know which one was used?
SDL_RWops vs C++ Streams
The code examples use SDL_RWops for file handling. Can't we just use regular C++ file streams? What's the advantage of SDL's approach?
Floating-Point Endianness
How do SDL's endianness functions handle floating-point numbers differently from integers?
Signed vs Unsigned Endianness
The examples all use unsigned integers. Do I need to handle endianness differently for signed integers?
Big-Endian Applications
If most modern CPUs are little-endian, why do we ever use big-endian format? What are the use cases?
Or Ask your Own Question
Purchase the course to ask your own questions