Advertisement

Beginning SDL2 - problems with simple code

Started by July 21, 2024 09:19 AM
5 comments, last by Alberth 1 month, 2 weeks ago

Hello - I am placing this here, as it seems the most appropriate place; please feel free to move this if it should be somewhere else!

I have come across a problem that I can't seem to figure out, and wonder if anyone would have any pointers?

I am writing some code in C++, using SDL2, and experimenting with threads. I have managed to make the problem reproducible in a fairly short piece of code, as follows (I will edit this if the code tags don't work!)

main.cpp:

<code>

#include <stdint.h>

#include <thread>

#include <SDL2/SDL.h>

#include "src/folder2/mylib.h"

void superloop_cplusplus();

int main( int argc, char* args[]){

if (SDL_Init(SDL_INIT_VIDEO)<0) {printf("init fail\n");} // print an error if fail

SDL_Surface* fbsurface = SDL_CreateRGBSurface(0, 800, 600, 32, 0xFF00, 0xFF0000, 0xFF000000, 0);

if (fbsurface == NULL) {printf("create surface fail\n");} // print an error if fail

mylibrary object; // create an object of type mylibrary

object.setvars(); // call a function to update some private vars

std::thread mythread_01(superloop_cplusplus); // call a very simple thread

printf("checking SDL_MUSTLOCK\n");

bool result = SDL_MUSTLOCK(fbsurface); // see if we need to lock our surface or not?

if (result == false) {printf("we don't need to lock this surface\n");}

printf("the check did not fail\n");

while (false == false) { // infinite loop

}

return 0;

}

void superloop_cplusplus()

{

while (false == false) {

// a thread that does an infinite loop

}

}

</code>

02 - the header file for the library

<code>

#ifndef HEADER_MYLIBRARY

#define HEADER_MYLIBRARY

class mylibrary{

public:

void setvars();

};

#endif

</code>

03 - the library itself

<code>

#include <stdint.h>

#include <string>

class mylibrary{

public:

void setvars();

private:

uint64_t var1;

uint64_t var2; // <-- writing to this one causes SDL2 to crash later on

uint64_t var3;

uint64_t var4;

};

void mylibrary::setvars(){

var1=1;

var2=1; // crashes SDL2

var3=1;

var4=1;

return;

}

</code>

(note - I am hoping that these code tags work, or that I can go back and edit / experiment to try to make them work)

(I did my best, but cannot change the default ‘wide spacing’)!

What I would hope would happen would be that I get some console output as follows:

checking SDL_MUSTLOCK

we don't need to lock this surface ** this may not appear if we do need to lock the surface

the check did not fail

What I actually get is :

checking SDL_MUSTLOCK

Hit any key to continue (the program crashes, silently, hence this prompt)

if I comment out the ‘var2=1’ line in my library, then the check of SDL_MUSTLOCK works fine, but if I leave that line in place, then, for some reason, it does something that makes SDL crash later when doing SDL_MUSTLOCK

I am new to C++ (having only programmed in assembly for years), so I know there is a stupid mistake somewhere, and would be very grateful for any pointers, having spent hours on this so far!

Thank you for any advice 🙂

As for code formatting, in the bar above the area where you type are near the right, a bunch of quotes. Next to it a rectangle with “<>” in it. That's the code-block symbol you want for longer code pieces.

I don't see much wrong in the code wrt memory management, so maybe the var2 assignment is a red herring?? The only weird thing I saw was the alpha mask in the SDL_CreateRGBSurface call, where the last parameter is 0 while I expected 0xFF.

Did you try compiling with debugging options and no optimizations, and examining the stack-dump of the crash? That should point out how it crashed.

You may also check what SDL2 assumes wrt threads. However as it doesn't do anything, I wouldn't know how that affects the program.

Advertisement

Thank you - I don't know how to get the debugger to work, or how to change optimisations … but I will be able to figure that out by searching for the particular setup I am using (g++, gas, codelite);

Thank you for the pointers; I tried fixing the alpha mask, but it made no difference to the error (but still it is good to correct that to avoid other problems later);

The actual code I am working on is written in pure ARM64 asm, and what I am doing is have the asm write to a framebuffer for various reasons (essentially, it will use a framebuffer like in the 1980s/1990s), so what I am doing is launching the asm in one thread, and keeping the SDL and other stuff in the ‘main’ CPP function. My plan is to add support for ‘advanced graphics’ (OpenGL, Vulkan, and so on) once I get the main code up and running, keeping the framebuffer as an advanced debug console, and the ‘terminal’ (I am on Linux) as an alternate debug console (I have found I can ‘lock’ the terminal by writing too much data at once, which is one reason for a simple framebuffer terminal where I can write the characters myself);

The inability to get the SDL ‘framebuffer’ to work is frustrating (I can get it to work, can print characters, but when I start running the asm in a seperate thread, the errors start). Regarding ‘thread safety’ … the asm is self-contained, will be writing to another (non-SDL) ‘framebuffer’ block of memory, and I can just copy that manually over to the SDL framebuffer periodically to avoid any real issues with threads / SDL locks etc. I already have the thread running, with output to console, without issue. I am running on a Raspberry Pi … not a typical gaming platform, but my aim is to develop games, and it is good for learning ARM64 code!

Interestingly, after your thoughts about, I discovered I can still reproduce the error without even launching that separate thread now. So … I removed the launching of the thread completely, then added some simple ‘printf debugging’ to examine the actual addresses of var1, var2, var3, var4, and fbsurface, and was surprised to find this:

var1 ptr = 0x7fffd44195b8; var1 value = 140734835029824

var2 ptr = 0x7fffd44195c0; var2 value = 93827557283488

var3 ptr = 0x7fffd44195c8; var3 value = 68719476742

var4 ptr = 0x7fffd44195d0; var4 value = 140736754456288

fbs_ ptr = 0x7fffd44195c0

checking SDL_MUSTLOCK

Hit any key to continue...

I will see how much further I can shrink down my code (maybe get it all into a smaller main.cpp file), but it appears that:

SDLSurface* fbsurface = SDLCreateRGBSurface(0,800,600,32, etc etc);

creates this SDLSurface at address 0x7FFFD44195C0,

and then:

mylibrary object; (which happens after the SDLSurface call); creates an object of type ‘mylibrary’, overwriting the SDLSurface that was created earlier :

it creates var1 - var4 at 0X7FFFD44195B8 / C0 / C8 / D0

So, it seems to be an issue with the same memory allocation, but seems to be a problem with C++ (g++?) itself!

I will explore further, but for now, a temporary fix seems to be to allocate some memory to a random object after creating the SDL stuff, and just leave that memory untouched, to ‘protect’ against the memory allocation failure (a temporary fix to allow development while figuring out what on earth is going on with the above), which will also allow me to see if I can reproduce the error using even less code, to figure out why this is going wrong in the first place 🙂

I have managed to shrink the code down a lot, and can still reproduce the error as follows;

main.cpp

#include <stdint.h>

#include <stdio.h>

#include "src/folder2/mylib.h"

int main( int argc, char* args[]){

uint64_t* fbsurface; // fbsurface is a pointer

mylibrary object; // create an object of type mylibrary

object.setvars(); // call a function from that library

printf("fbs_ ptr = %p\n",&fbsurface); // address of fbsurface

return 0;

}

mylib.h

class mylibrary{

public:

void setvars();

};

mylib.cpp

#include <stdint.h>

#include <string>

class mylibrary{

public:

void setvars();

private:

uint64_t var1;

uint64_t var2; // <-- writing to this one causes SDL2 to crash later on

uint64_t var3;

};

void mylibrary::setvars(){

printf("var1 ptr = %p",&var1);

printf("; var1 value = %ld\n",var1);

printf("var2 ptr = %p",&var2);

printf("; var2 value = %ld\n",var2);

printf("var3 ptr = %p",&var3);

printf("; var3 value = %ld\n",var3);

return;

}

output:

var1 ptr = 0x7fffe1ee53a0; var1 value = 140736983881000

var2 ptr = 0x7fffe1ee53a8; var2 value = 0

var3 ptr = 0x7fffe1ee53b0; var3 value = 140736983880896

fbs_ ptr = 0x7fffe1ee53a8

Hit any key to continue...

(I can't change the ‘large spacing’ between each line, as that seems to be a feature of the forum interface?)

OK … I figured it out!

The problem was that I was only putting the ‘public’ variables of the class into the class header file! It turns out that you need to also put the ‘private’ data variables into the header file for a class, otherwise C++ won't allocate memory for them (when you create an object of that class), but C++ will allow member functions of that class to access the ‘private’ variables, although it didn't actually create them!

I can find a lot of guides online saying that you shouldn't declare private variables in header files, and plenty saying that you should!

It turns out the fix is that, for classes, you need to actually keep the entire class definition in the header file, rather than a .cpp file (although you can put the ‘functions / members’ into a cpp file using class::function(){}, to reduce the size of the header file (or at least, this is what needs to be done on Debian g++!)

Great!

I completely missed the private variables, but indeed, if you want to store them in a class, it should know about them 🙂

----

You can have have .h files without them, but then you go in the direction of an interface. You describe the externally visible parts in a class in a header file (abstract functions is sufficient). Basically, it's the public API of the class that you write there.

Then you write an implementation class as well, (eg in a cpp file) that inherits from the class in the header file, but it also has all implementation details. I never did this in CPP, but the advantage is that the class in the header file has no implementation details, it's a pure abstraction. (The implementation details are hidden.)

----

Where you write a class definition makes no difference at all in general, except you need the class definition in all contexts where you use it, and a header file with 1 definition is a lot simpler to manage when a class is used in many contexts.

----

For g++ at debian, g++(1) will work (ie “man g++”). An online html version is at https://www.gnu.org/software/gcc/​

For example:

CCFLAGS="-Wall -g"
FLEXFLAGS=

bison --defines=tokens.h --output=parser.cpp parser.y
flex $FLEXFLAGS --outfile=scanner.cpp scanner.l

g++ $CCFLAGS -c -o parser.o parser.cpp
g++ $CCFLAGS -c -o scanner.o scanner.cpp
g++ $CCFLAGS -c -o encode.o encode.cpp
g++ $CCFLAGS -c -o ast.o ast.cpp
g++ $CCFLAGS -c -o image.o image.cpp
g++ $CCFLAGS -c -o output.o output.cpp

g++ $CCFLAGS -o encoder parser.o scanner.o encode.o ast.o image.o output.o -lpng

Ignore the “bison” and “flex” calls, they are code generators.

The CPPFLAGS contain the compile settings, -Wall increases the number of warnings you get on the code. -g enables debugging support. Not done here, but -Ox for various `x` (such as -O2 or -O3) enables optimizing.

Basically you compile each c++ file to an object file, with -c -o name.o (compile only, and write the result to name.o).

Once all files are compiled you run the linker at the bottom line. The -lpng includes the libpng library. The -o encoder specifies the output file.

The above script works for small programs. If it grows you'll want to compile in parallel. A Makefile can help you there. Alternatively, an IDE usually knows how to build from source and may do such things for you.

EDIT: At least as important, a Makefile can also handle skipping compiles of unchanged files !!

This topic is closed to new replies.

Advertisement