Detecting and Managing Errors

Discover techniques for detecting and responding to SDL runtime errors
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

As with any code, interacting with SDL can sometimes result in errors. For example, let’s imagine we’re trying to create a window that uses Metal, which is Apple’s API for creating high-performance graphics. To do this, we pass the SDL_WINDOW_METAL flag to SDL_CreateWindow():

#include <SDL.h>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL
  );
  
  return 0;
}

If this program is run on a platform that doesn’t support Metal (such as a Windows machine), window creation will fail, and it might not be obvious why that is. In this lesson, we’ll introduce the ways we can detect and cover from errors coming from SDL.

SDL_GetError()

The SDL_GetError() function is the main way we retrieve information about errors coming from SDL. It returns a string, representing the most recent error that occurred.

If we call this function after attempting to create our window, we’ll get more information about what’s going wrong:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL
  );

  std::cout << SDL_GetError();

  return 0;
}
Metal support is either not configured in SDL or not available in current SDL video driver (windows) or platform

SDL_GetError() can only return the most recent error message. In the following example, we have two errors - one when we create the window, as before, and a second error when we then try to get the surface associated with that window:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL 
  );

  SDL_GetWindowSurface(Window);

  std::cout << SDL_GetError();

  return 0;
}

When we called SDL_GetError(), the most recent error was from the SDL_GetWindowSurface() call. SDL_CreateWindow() failed in the previous example but information about that error was overwritten by the error from SDL_CreateWindow():

Invalid window

Additionally, SDL_GetError() does not clear the error state. Subsequent calls to SDL_GetError() will return the same message until a new error occurs:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_GetWindowSurface(nullptr);

  std::cout << SDL_GetError();
  std::cout << SDL_GetError();
  std::cout << SDL_GetError();

  return 0;
}
Invalid window
Invalid window
Invalid window

The char* Data Type

In previous lessons, we’ve been using std::string when working with strings of text. SDL uses a more primitive type, sometimes called a C-style string. It is a char* - that is, a pointer to an individual character (char):

const char* Error = SDL_GetError();

Like any pointer, a char* represents a location in memory. In this case, it represents the first character in the string.

Subsequent characters are then stored in subsequent memory addresses, until we reach a special character called the null terminator, which is represented by \0:

Diagram showing a character pointer pointing at a c-string in memory

When code such as std::cout receives a c-style string, it understands that the string starts at that memory address pointing to the first character (the char*, that each subsequent character is in the next memory address, and that the string continues until the null terminator \0 is found.

If the first character in a string is the null terminator \0, that is equivalent to the string being empty:

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

int main(int argc, char** argv){
  SDL_Init(SDL_INIT_VIDEO);

  const char* Error = SDL_GetError();
  if (*Error == '\0') {
    std::cout << "There is no error";
  } else {
    std::cout << "Error: " << Error;
  }

  return 0;
}
There is no error

C-Style Strings and std::string

Often, we’ll want to use a more powerful and modern string representation, such as std::string, in our code. This means we will need to deal with conversions when interacting with the SDL API.

std::string has a constructor that accepts a char*, so converting a string returned from SDL to a std::string is easy:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);

  std::string Error = SDL_GetError();
  if (Error.empty()) {
    std::cout << "There is no error";
  } else {
    std::cout << "Error: " << Error;
  }

  return 0;
}
There is no error

When we have a std::string and need an equivalent char* to pass to some SDL function, the c_str() method can help us:

#include <SDL.h>
#include <string>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);

  std::string GameName{"My Game"}; 
  SDL_CreateWindow(
    GameName.c_str(), 
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768, 0
  );

  return 0;
}

Our advanced course has a dedicated chapter that goes much deeper on C-style strings, std::string, and more.

SDL_ClearError()

Once we’ve processed and acknowledged an error, we can call SDL_ClearError() .

This will cause SDL_GetError() to return an empty string, unless another error has occured between the SDL_ClearError() and SDL_GetError() calls:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_GetWindowSurface(nullptr);
  std::cout << "Error: " << SDL_GetError();
  SDL_ClearError();
  std::cout << "\nError: " << SDL_GetError();

  return 0;
}
Error: Invalid window
Error:

Creating an Error Handler

If we’re going to be doing a lot of error checking, it can be helpful to create a simple function that makes this easier. The following function:

  • Accepts a string, letting the caller explain what action is associated with the error check
  • If SDL_Error() returns a non-empty string, it logs that error, as well as the action that caused it for context
  • Clears SDL’s error string using SDL_ClearError()
// ErrorHandling.h
#pragma once
#include <SDL.h>
#include <iostream>

void  CheckSDLError(const std::string& Action) {
  const char* error = SDL_GetError();
  if (*error != '\0') {
    std::cout << Action << " Error: "
      << error << '\n';
    SDL_ClearError();
  }
}

We can use it like this:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL 
  );
  CheckSDLError("Creating Window");

  SDL_GetWindowSurface(Window);
  CheckSDLError("Getting Surface");

  std::cout << "Hello World\n";
  // No error has occurred since the last call
  // to CheckSDLError, so this won't log anything
  CheckSDLError("Saying Hello");

  return 0;
}
Creating Window Error: Metal support is either not configured in SDL or not available in current SDL video driver (windows) or platform
Getting Surface Error: Invalid window
Hello World

We may want to disable calls to this function in our final released games, so we can have the preprocessor include them only if some macro is defined:

// ErrorHandling.h
#pragma once

// Uncomment me to add error logging:
// #define ERROR_LOGGING

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL 
  );
  #ifdef ERROR_LOGGING
  CheckSDLError("Creating Window");
  #endif

  SDL_GetWindowSurface(Window);
  #ifdef ERROR_LOGGING
  CheckSDLError("Getting Surface");
  #endif

  std::cout << "Hello World\n";
  #ifdef ERROR_LOGGING
  CheckSDLError("Saying Hello");
  #endif

  return 0;
}
Hello World

Detecting Specific Errors

In most advanced programs, our error handling will often want to go beyond simply logging out what went wrong. We’ll often want to implement logic that reacts and potentially recovers from certain types of errors.

In addition to updating the string returned by SDL_GetError(). SDL functions will often use their return values to indicate something went wrong.

We need to refer to the official documentation for information on any specific function. However, as a general pattern, functions that return a pointer, such as SDL_CreateWindow(), will return a nullptr if something went wrong:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window = SDL_CreateWindow(
    "Hello World",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    1024, 768,
    SDL_WINDOW_METAL 
  );

  if (!Window) {
    std::cout << "Couldn't create window -"
                 "trying without Metal\n";

    Window = SDL_CreateWindow(
      "Hello World",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      1024, 768,
      0 
    );
  }

  if (Window) {
    std::cout << "Window created successfully";
    SDL_ClearError();
  }

  return 0;
}
Couldn't create window: Metal support is either not configured in SDL or not available in current SDL video driver (windows) or platform
Trying without Metal: Window created successfully

Other functions that report errors will typically do so through an integer they return, with negative values indicating an error. For example, SDL_UpdateWindowSurface() returns 0 if it was successful, or a negative value if it failed:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  if (SDL_UpdateWindowSurface(nullptr) < 0) {
    std::cout << "Surface update failed: "
              << SDL_GetError();
  }

  return 0;
}
Surface update failed: Invalid window

SDL_SetError()

While SDL_SetError() is primarily used internally by SDL functions, you can also use it in your own code if you want to reuse SDL's error reporting system for your own custom errors.

This can be useful when you want to use a consistent error handling mechanism throughout your application, even for non-SDL related errors:

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

int Divide(int a, int b) {
  if (b == 0) {
    SDL_SetError("Cannot divide by 0");
    return 0;
  }
  return a / b;
}

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Divide(2, 0);
  std::cout << SDL_GetError();

  return 0;
}
Cannot divide by 0

Summary

In this lesson, we've explored essential techniques for handling errors when working with SDL. We learned:

  • How to use SDL_GetError() to retrieve error messages
  • An introduction to working with C-style strings, and how they interact with std::string
  • The use of SDL_ClearError() to manage the error state
  • How to implement custom error handling functions
  • How to check for specific errors by examining the return values from SDL functions
  • Calling SDL_SetError() to use SDL to handle our own custom error states

Was this lesson useful?

Next Lesson

Implementing an Application Loop

Step-by-step guide on creating the SDL2 application and event loops for interactive games
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Detecting and Managing Errors

Discover techniques for detecting and responding to SDL runtime errors

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Creating an SDL Application
  • 31.Engine Overview
  • 32.Creating the Grid
  • 33.Placing the Bombs
  • 34.Handling Adjacent Cells
  • 35.Ending and Restarting Games
  • 36.Placing Flags
  • 41.GPUs and Rasterization
  • 42.SDL Renderers
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 36 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Implementing an Application Loop

Step-by-step guide on creating the SDL2 application and event loops for interactive games
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved