All blog entries, ordered from most recent. Entry count: 1195.
# 3 Ways to Iterate Over std::vector
Sat
30
Sep 2023
This will be a short article about basics of C++. std::vector is a container that dynamically allocates a continuous array of elements. There are multiple ways to write a for loop to iterate over its elements. In 2018 I've written an article "Efficient way of using std::vector" where I compared their performance. I concluded that using iterators can be orders of magnitude slower than using a raw pointer to its data in Debug configuration. This time, I would like to focus on how using "modern" C++ also limits our freedom.
Language purists would probably say that the recommended way to traverse a vector is now a range-based for loop, available since C++11. This is indeed the shortest and the most convenient form, but inside the loop it gives access only to the current element, not its index and not any other elements.
struct Item
{
int number;
int otherData[10];
};
std::vector<Item> items = ...
int numberSum = 0;
for(const Item& item : items)
numberSum += item.number;
Imagine that while traversing the vector, for some elements that are not the first and that meet certain criteria, we want to compare them with their previous element. This is not possible in a range-based for loop above, unless we memorize the previous element in a separate variable and update it on every iteration. Using iterators gives us the possibility to move forward or backward and thus to access the previous element when needed.
for(std::vector<Item>::const_iterator currIt = items.begin(); currIt != items.end(); ++currIt)
{
if(currIt != items.begin() && // Not the first
MeetsCriteria(*currIt))
{
std::vector<Item>::const_iterator prevIt = currIt;
--prevIt; // Step back to the previous element
CompareWithPrevious(*prevIt, *currIt);
}
}
This is more flexible, but what if we want to insert some elements to the vector while traversing it? There is a trap awaiting here because insert method may invalidate all iterators when underlying array gets reallocated. This is why only iterating using an index is safe here:
for(size_t index = 0; index < items.size(); ++index)
{
Item newItem;
if(NeedInsertItemBefore(items[index], &newItem))
{
items.insert(items.begin() + index, newItem);
++index;
}
}
Note that pretty much any modern programming language allows to insert and remove elements from a dynamic array using an index, e.g.:
List methods Insert, RemoveAtArrayList methods add, removespliceinsert, popVec methods insert, remove that take index as parameter.Only C++ requires clumsy syntax with iterators like items.begin() + index.
I know that the code fragments shown above can be written in many other ways, e.g. using auto keyword. If you have an idea for writing any of these loops better way, please leave a comment below and let's discuss.
# ShaderCrashingAssert - a New Small Library
Sun
20
Aug 2023
Last Thursday (August 17th) AMD released a new tool for post-mortem analysis of GPU crashes: Radeon GPU Detective. I participated in this project, but because this is my personal blog and because it is weekend now, I am wearing my hobby developer hat and I want to present a small library that I developed yesterday:
ShaderCrashingAssert provides an assert-like macro for HLSL shaders that triggers a GPU memory page fault. Together with RGD, it can help with shader debugging.
Comments | #rendering #directx #productions #libraries #gpu #tools Share
# Ways to Print and Capture Text Output of a Process
Sun
02
Jul 2023
In my previous blog post “Launching process programmatically: system vs CreateProcess vs ShellExecute”, I investigated various ways of launching a new process when programming in C++ using Windows, with the focus on different ways to specify a path to the executable file. Today, I want to describe a related topic: we will investigate ways that a process can print some text messages (standard output, standard error, WriteConsole function, DebugOutputString function), how we can observe this output and, finally, how we can capture it programmatically when launching a subprocess using CreateProcess function.
Visual Studio / C++ project accompanying this article: github.com/sawickiap/TextOutputTest
Comments | #windows #winapi Share
# Launching process programmatically: system vs CreateProcess vs ShellExecute
Sat
15
Apr 2023
Today I went on a quest to investigate various ways in which we can launch a process (an EXE file) programmatically, while programming in C++ using Windows. I tested 3 different functions: system, CreateProcess, ShellExecute. I focused on ways to specify a path to the executable file – not on passing parameters and not on capturing standard input/output of the subprocess (which I plan to investigate next and I did). All examples below launch a subprocess and wait until it completes before continuing. They all make the subprocess inheriting the console, so if both main process and the subprocess are console programs, their output will go to the single console of the main process.
But first things first: To understand this article, please recall that in operating systems we commonly use, no matter if Windows or Linux, every executable file launched as a process has several parameters:
Paths in the file system can be absolute (in case of Windows it usually means they start with drive letter, like “C:\Dir1\Text.exe”) or relative.
Startup directory is often the same as the directory where the executable file is located, but it doesn’t need to be. Many methods of process launching offer an explicit parameter for it. We won’t use it in the code samples below, but you can also achieve this manually from system console. For example, following console command uses a relative path to launch an executable located in “C:\Dir2\Test.exe”, while current directory of the process will be the same as current directory of the console: “C:\Dir1”:
C:\Dir1>..\Dir2\Test.exe
Method 1: Function system from standard C library (required header: <stdlib.h> or <cstdlib> in C++) is the simplest, most primitive one. It just takes a single string as parameter. An advantage of it is that you can launch any console command with it, also built-in commands (like “echo”), not only EXE files. It is also portable between different operating systems.
#include <cstdlib>
int main()
{
char path[MAX_PATH];
strcpy_s(path, "Test.exe");
system(path);
}
Waiting for the command to finish is the default behavior of this function and so is inheriting the console, so that messages printed to the standard output by “Test.exe” will go to the same console as our host application.
path can always be absolute or relative. For each of the 4 methods described in this article, I found answers to following questions:
strcpy_s(path, "C:\\My Program\\Test.exe");? No. (Note the double backslash \\ is for escaping in C++, so that string will actually contain single backslashes. You can also use forward slashes / in Windows – they work with all methods described in this article and they don’t need to be escaped in C++ code.)strcpy_s(path, "\"C:\\My Program\\Test.exe\"");? Yes.^, like strcpy_s(path, "C:\\My^ Program\\Test.exe");? Yes! (However strange it looks, this is the character used as an escape sequence in Windows shell!)Method 2: Function CreateProcess from WinAPI (required header: <Windows.h>) is likely the most native and most feature-rich option. Numerous parameters passed to the function and accompanying structures allow to control the new subprocess in various ways, including getting and using its process handle or capturing its standard input/output. Here, for simplicity, I replicate the behavior of system function from method 1 – I make it inherit the console by passing parameter bInheritHandles = TRUE and wait until it completes by calling WaitForSingleObject on the process handle. Process handle and main thread handle also need to closed to avoid resource leak.
STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION processInfo = {};
BOOL success = CreateProcess(
path, // lpApplicationName
NULL, // lpCommandLine
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
TRUE, // bInheritHandles
0, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
&startupInfo,
&processInfo);
assert(success);
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
There are actually 2 ways to pass executable file path to CreateProcess. Code above shows the first way – using lpApplicationName parameter, which is intended for just application name, while command line parameters are passed via next argument. Note this is different from system function, which accepts one string with everything. Using the method shown above:
"C:\\My Program\\Test.exe"? Yes – likely because this parameter is intended exclusively for executable file path."\"C:\\My Program\\Test.exe\""? No.^, like "C:\\My^ Program\\Test.exe"? No.Method 3: Function CreateProcess, but this time passing executable file path as lpCommandLine parameter, while leaving lpApplicationName set to NULL. This is also a valid use case and it behaves differently – more like launching a console command than starting a specific EXE file.
STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION processInfo = {};
BOOL success = CreateProcess(
NULL, // lpApplicationName <- !!!
path, // lpCommandLine <- !!!
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
TRUE, // bInheritHandles
0, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
&startupInfo,
&processInfo);
assert(success);
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
"C:\\My Program\\Test.exe"? No!"\"C:\\My Program\\Test.exe\""? Yes.^, like "C:\\My^ Program\\Test.exe"? No!Method 4: Function ShellExecuteEx (or legacy ShellExecute) which is also part of WinAPI, but coming from header <shellapi.h>. It requires COM to be initialized with CoInitializeEx. It can be used not only to start processes from EXE files, but also to open any types of files (TXT or DOCX documents, JPEG images etc.) with their associated programs, as if the user double-clicked on such file or right-clicked and selected one of the available “verbs”, like “Edit” or “Print”. But for this article, let’s focus on launching executable files. To replicate the same behavior as in previous methods, I pass SEE_MASK_NO_CONSOLE to inherit console and SEE_MASK_NOCLOSEPROCESS to retrieve process handle to be able to wait for it.
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO shellExecuteInfo = {
.cbSize = sizeof(SHELLEXECUTEINFO),
.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
.lpFile = path,
.nShow = SW_SHOWNORMAL
};
BOOL success = ShellExecuteEx(&shellExecuteInfo);
assert(success);
WaitForSingleObject(shellExecuteInfo.hProcess, INFINITE);
CloseHandle(shellExecuteInfo.hProcess);
This method behaves in the following way:
"C:\\My Program\\Test.exe"? Yes."\"C:\\My Program\\Test.exe\""? Yes.^, like "C:\\My^ Program\\Test.exe"? No.To summarize, let’s see all the results in a table:
| system() | CreateProcess() lpApplicationName |
CreateProcess() lpCommandLine |
ShellExecuteEx() | |
|---|---|---|---|---|
Works without extension? "Test" |
Yes | No | Yes | Yes |
| Searching dir of the host app? | No | No | Yes | No |
| Searching current dir? | Yes | Yes | Yes | Yes |
| Searching PATH env var? | Yes | No | Yes | Yes |
Path with spaces unescaped: My Program\Test.exe |
No | Yes | No | Yes |
Path with spaces enclosed with quotes: "My Program\Test.exe" |
Yes | No | Yes | Yes |
Spaces escaped with ^: My^ Program\Test.exe |
Yes | No | No | No |
I did my tests using Windows 10, Version 22H2 (OS Build 19045.2846) and Visual Studio 2022 17.5.3. Although unlikely, it is not impossible that these results may change on another version of the operating system or C++ compiler and standard library implementation.
Comments | #windows #c++ #winapi Share
# Book review: C++ Initialization Story
Mon
27
Mar 2023
Courtesy its author Bartłomiej Filipek (author of cppstories.com website), I was given an opportunity to read a book “C++ Initialization Story". Below you will find my review.

How many ways are there to initialize a variable in C++? I can think of at least the following:
int i1;
int i2; i2 = 123;
int i3 = 123;
int i4(); // function declaration not a variable
int i5(123);
int i6 = int(123);
int i7{};
int i8 = {};
int i9 = int{};
int iA{123};
int iB = {123};
int iC = int{123};
Do you know the difference between them? Which variable stays uinitialized, which is initialized with a value 0 or 123? What if I used a custom type instead of the basic int? How many copies of the object would be created? What if that type was a class having some custom constructors? Which constructor would get called? What if it was a std::vector or some other container?
Question like this is the foundation of this book, but topics covered by it are much wider. This book is a relatively big one. On 279 pages, the author treats the topic of "initialization" as an opportunity to describe various concepts of C++ language. Modern versions of the language standard are covered, up to C++23, but features that require new versions are explicitly marked as such. The book is not about some exotic quirks and tricks that can be done by stretching the language to its limits, but it is about concepts that are fundamental in any C++ program.
Initialization of local variables, as shown in the code above, is just the subject of the first chapter. Then initialization of "non-static data members" is described, which basically means variables inside structures and classes. Constructors obviously play the major role here, so their syntax and behavior is also described in details here. When talking about constructors, description of assignment operators and destructors follows naturally. Of course, these language constructs are described also in light of move semantics introduced by C++11. For example, did you know that std::vector<T> on resize will be able to use move constructor of your type T instead of performing a copy only when the move constructor is marked as noexcept?
Another topic related to initialization is an automatic deduction of types: auto keyword and template arguments. Special kinds of variables - static and thread_local are also described. The book also teaches new language constructs added for convenient variable initialization, like structured binding, designated initializers, or static inline. If you only used the old version if C++ so far, do you know that following syntax is now possible? Do you know what it means?
auto[iter, inserted] = mySet.insert(10);
Point p {
.x = 10.0,
.y = 20.0
};
class C {
static inline int classCounter = 0;
When it comes to the difficulty level of the book, I would call it intermediate. Only some knowledge of C++ is required. Author explains every topic covered from very basics and shows simple code samples. The book additionally features a quiz in the middle and at the end, as well as a chapter with "techniques and use cases". For example, did you know that the most robust and efficient way to initialize a class with a string is to pass it by... value?
struct StringWrapper {
std::string str_;
StringWrapper(std::string str) : str_{std::move(str)} { }
For a long time I've been skeptical about new language standards like C++11, C++14, C++17, C++20. C++ is a tough language already, so every fresh addition only adds more complexity to it. It used to remind me of some elaborated, tricky Boost-style templates. But now, the more I use new features of the language (at least in my personal code), the more I like it. I always liked RAII and unique_ptr, but now with move semantics, return value optimization, std::optional, std::variant, and many other additions to the language small and big, it all starts to fit together. Code is clean, concise, readable, safe (no explicit new or delete!), and efficient at the same time. I now think that it is not an inherent feature of C++ to be verbose (with tons of boilerplate code required) and unsafe (with memory access violation errors easy to make), it is the old-fashioned approach of treating is as "C with classes". I hope that over time more and more developers, especially those who make key decisions in software projects, will also notice that and will allow using modern C++.
The book can be bought as ebook on leanpub.com, as well as in printed version on Amazon. I can strongly recommend it - it is really good! See also my reviews of previous book by this author: "C++17 in Detail" and "C++ Lambda Story".
# Impressions from Vulkanised 2023 Conference
Thu
16
Feb 2023
Last week I attended Vulkanised conference. It is an official conference of Vulkan API. It took place 7-9 February 2023 in Munich, Germany. It was my first time at this conference. My attendance was part of my job at AMD and I co-presented with Valve about using Radeon Developer Tools on RADV (Linux AMD driver) and Steam Deck. Here, on my blog, I would like to share my personal impressions from the event.
Overall, it was well organized. There were over 200 attendees, 3 days full of talks, most of them short (20-30 minutes, some of them even 10 minutes!), happening on just one scene (apart from full-day Vulkan tutorial for beginners, happening on the first day in parallel with normal talks), with lunch break and coffee breaks in between, so everyone could see everything without a need to choose from the timetable which talks to attend. It was intense. Every evening we went for some good food and beer, which I enjoy a lot every time I visit Munich/Bavaria/Germany.
In terms of people attending, a conference like this differs completely from game developer conferences that I usually attend. On one hand, everyone there was a programmer who knows and uses Vulkan, so everyone was on the same page. On gamedev conferences, there are people from different fields, as game development is multidisciplinary - graphics and music artists, designers, programmers, business people etc. On the other hand, there were not so many people from game industry there, and if anyone, they were mostly from the world of mobile GPUs, not PC or console. It was interesting to talk with developers from various industries, using GPUs and Vulkan for different applications, like scientific computations and visualizations or even… software for cloth design for fashion business.
There were many interesting talks. I think the most valuable ones were about components of the Vulkan ecosystem that are useful to every developer, like Vulkan validation layers, VkConfigurator, Vulkan loader, or GFXReconstruct (which also added support for Direct3D 12 recently, by the way!). There were long and extensive talks teaching two recent big additions to the API: mesh shaders and Vulkan Video. Vulkan Video seems to be especially complicated, partially because it requires some knowledge of video encoding/decoding, which is something different from 3D rendering. I used to work for television, so it was not that obscure for me. But this new part of the API is also very low level. The decision to make encoding/decoding of every frame stateless, with all the state of the video stream managed by the user, makes the API surface very extensive.
Talk about Diligent Engine was interesting. I didn’t look at the project itself, but the presentation looked convincing that this is a good multi-platform 3D graphics library implemented on top of various graphics APIs. Another interesting project presentation was about VkFFT - a C library that calculates FFT on the GPU using one of many supported APIs (not only Vulkan) with state-of-the-art performance. It is implemented by assembling a string with the source code of a kernel optimized for a specific case.
Presentations about game optimization for mobile GPUs were very interesting to me. Optimizing games is what I do in my everyday job, although I work with “large” PC GPUs. I consider such talks with a collection of tips and recommendations exceptionally valuable. From these presentations, I could learn what things work fast on smartphone and tablet chips, which are different from PC and console chips. They said that on these platforms, energy consumption and bandwidth to and from memory is the most important. Because mobile GPUs are tile-based, a large amount of vertices or fat vertex format is very slow, which is not the case on PC. Also because of that, they recommend to group as many passes as possible as sub-passes of a single Vulkan render pass, even to a degree that rendering of 3D objects could be grouped together with screen-space postprocessing effects. Again, it isn’t a thing that we normally do on PCs. It was also interesting to see how they measure performance. While I always disable V-sync and just measure FPS in games, they seem to give multiple columns with results, including FPS, but also GPU utilization %, which is likely used when reaching 60 FPS with V-sync always enabled.
But more than any specific presentation, it was interesting for me to hear some general ideas about Vulkan, often repeated by multiple people. There were people from Khronos and LunarG there (the company that develops Vulkan SDK), so we could hear from and ask questions to people who really make this API. There was a discussion panel with many prominent participants who shared their voice on these topics. Noone said “what happens on Vulkanised stays on Vulkanised”, so here are some things I remember. Disclaimer: These are my personal, subjective impressions. I might remember something wrong. Please feel free to leave a comment with your own thoughts below this article.
Some profound things have been said about Vulkan. Someone said it’s not a graphics API, more like a Hardware Abstraction Layer (HAL) or an API for programming accelerators. They said it is a “design by compromise” rather than “design by committee”. They said we should think of Vulkan as not only the specification, by the entire ecosystem, including libraries, tools, code samples, learning materials, etc. I was pleased to hear that Vulkan Memory Allocator that I maintain was often mentioned as one of the examples. An open question is how many of these 3rd party components should be considered “canonical”. Many are already included in Vulkan SDK, but should official samples use them as well? Currently, they don’t, as they teach raw Vulkan. Someone also said that these ecosystem components should be properly funded. Another question was about the direction Vulkan should go. One person said it should probably become even more low-level, with app-space libraries on top of it more widely used.
It was surprising to see that there are solutions to run Vulkan above and below every other graphics API, which makes Vulkan a common ground across systems and APIs:
Among problems that developers have with using Vulkan and potential areas of development for the future, I noticed several common themes:
Overall, participation in Vulkansed conference was a great experience for me. I wish I will come back there. But Vulkan, even with its unprecedented openness, portability, and universality, is just part of the entire world of 3D graphics programming. On a conference dedicated to Vulkan I wouldn’t say loud that Direct3D 12 is more popular among PC game developers and it is not without a reason, or that maybe both these “explicit” APIs are at the worst possible level of abstraction - low level enough to be difficult to learn, to use, and easy to create bugs, while high-level enough to still hide hardware details crucial to squeezing maximum performance. But this is a separate topic…
When attending any event, I always pay attention to the quality of the audio-video system. On Vulkanised, it was very good. I especially liked the acoustics of the room, which clearly someone paid attention to when designing the interior. But there were some issues with presentation video that I don’t see too often. I blogged before about 3 Rules to Make You Image Looking Good on a Projector, where I mentioned potential problems with contrast, reproduction of colors or thin lines. Another time I described a possibility that edges of the screen may be cropped. But this conference had a different problem. Instead of connecting their laptops to a HDMI cable, speakers were asked to join an online meeting via Google Meet and share their screen there, with presentation on the big screen by another participant of that virtual call, streaming the content. We were in a Google office, after all :) This surely helped them record the presentations easily, but it also made any video or animation degraded to what looked like 2 FPS.
For more photos, see the official gallery 2023 Vulkanised by Khronos.
Comments | #rendering #vulkan #events Share
# Impressions After Global Game Jam 2023
Tue
14
Feb 2023
I usually write technical blog posts to educate readers on specific topics. However, this time, I wanted to share something more personal - my experience after participating in the Global Game Jam 2023. The event took place from February 3 to 5, 2023, but only now did I find the time to write this post, as I spent a week in Munich attending the Vulkanised conference right after GGJ.

For those unfamiliar with the Global Game Jam, it's a worldwide event where participants come together to create games for fun. Unlike Ludum Dare, GGJ is not just an online/remote event. It's an opportunity to spend the weekend in person at one of many sites around the world and develop a game based on a specific, globally-announced theme within the constrained time limit. In Poland alone, there were eight sites organized in major cities. I attended PolyJam 2023 (GGJ entry, FB event), which was organized by Koło Naukowe Twórców Gier Polygon, a game development interest group at Warsaw University of Technology that I still regularly attend even though I'm no longer a student.
The theme announced for this year’s GGJ was “roots”. A theme is something that games made during the jam should be related to, or at least be inspired by. But the theme can be interpreted freely. Roots of trees and other plants are the first association that comes to mind and that most teams followed (including us), but others are also possible, e.g. a heritage like genes or culture inherited from parents and ancestors, something about indigenous people, or even… calculating mathematical square root.
Our jam site was large and well organized. KNTG Polygon has long experience in organizing such events, after many years of doing local site of GGJ, as well as their custom Slavic Game Jam. Thanks to the work of volunteers and money from sponsors, a very low entry fee ensured not only space, access to the power and Internet but also unlimited coffee, other drinks, sweets, and full catering. GGJ website says there were 124 jammers registered on the site. Although GGJ as a whole isn’t a competition, there are no winners or prizes, our local site featured a competition.
When competitions are made on game jams, there are 2 general ways of doing them:
If a team wants to win, they should take different approaches depending on this. In option 1, the game is played only by the authors, so it is enough to prepare a good-looking show for a couple of minutes. However, they need to think beforehand about what to say and how to play their game to impress people. Option 2 is essentially like preparing a booth on a gaming expo – all about attracting people, showing and explaining the game to them, and making sure the build works fine and looks playable during these few minutes when other people play it. PolyJam 2023 went for option 1. Every team had 3 minutes to present their game on stage. There were over 40 different teams, but the presentation was well organized and went smoothly.
Back to my presence there… I didn’t take part in a game jam for 2 years, since before COVID. I wanted to go there to check if I still remember how to program :) Of course I work with code in my everyday job, but quickly hacking a game jam game, which is essentially like a prototype, is something different from writing production-quality code at work. The small 2D game we made is: Roots of Life and Death. Our team was 3 people: Michał Rudnicki “Mildanach” as graphics artist, Bartek Dramczyk “Voyager” who made music and sound effects, and myself as the programmer. We’ve developed everything on a public GitHub repository. I also hosted web version of the game that can be played online. The game is about resource management – by creating new nodes (left mouse button click) and transferring resources between them (left mouse button drag&drop), player can expand the system of underground roots, create new flowers to gather more sun at the top of the map and acquire more water at the bottom. Enemy plant is playing on the other side of the screen according to the same rules, controlled by the AI.
I know the game is not finished, not very dynamic or enjoyable. What is important to me is the way we made it. In past game jams I used different technologies, ranging from a custom engine in C++, Cocos2d-x library, to Unity and Unreal Engine, which are the most popular these days. I must admit I don’t know Unity or UE too well – not as much as I wish I knew, but for this year’s GGJ I decided to try something new: I used Cocos Creator. This is a Chinese game engine that looks somewhat similarly to Unity, provides a convenient editor, features a component-based scene graph, and supports all an indie game may need (2D and 3D graphics, collisions and physics simulation, UI, sound, etc.).
The programming language used in it is TypeScript. I didn’t know either Cocos Creator or TypeScript before. Having only basic knowledge of JavaScript, I started learning them 2 weeks before the jam. I enjoyed it a lot. It is long time since I learned a new programming language, while it is always a very mind-expanding experience. I like the way TypeScript introduces strong typing into JavaScript, which is by nature a very dynamic scripting language. For example, let a: string|number; defines a variable which can contain either string or numeric values, while let eventType: 'mouseDown'|'mouseUp'; defines a variable that can hold only a string with one of these two specific values. I was learning just from a first TypeScript tutorial I found on the Internet and the official TypeScript cheat sheets.
With our game, we didn’t win the competition at our site and we were far from winning, but this wasn’t the point. I am still happy about our performance. Things that went well:
What went wrong:
Overall, participation in Global Game Jam was a fun experience. I can recommend it to everyone who likes games and feels a need to do something creative. There is no need to have a team beforehand. Some people just come and team up with freshly meet people, some make their games alone as a 1-person team. I even met some people who came but didn’t plan to make any game! They just wanted to spend this time among nice, like-minded people and do something creative, e.g. to draw new things to their personal portfolio.
Comments | #javascript #events #competitions #ggj Share
# Why I Catch Exceptions in Main Function in C++
Sun
22
Jan 2023
Exception handling in C++ is a controversial topic. On one hand, it can be a good means of reporting and handling errors if done correctly. For it to be free from memory leaks, all memory allocations should be wrapped in smart pointers and other acquired resources (opened files and other handles) wrapped in similar RAII objects. On the other hand, it has been proven many times that the exception mechanism in C++ works very slow. Disabling exception handling in C++ compiler options can speed up the program significantly. No wonder that game developers dislike and disable them completely.
Let’s talk about a command-line C++ program that doesn’t need to disable exception handling in compiler options. Even if it doesn’t use exceptions explicitly, some exceptions may occur, thrown by C++ standard library or some third-party libraries. When a C++ exception is thrown and uncaught, program terminates and process exit code is some large negative number. On my system it is -1073740791 = 0xC0000409.
It would be good if the program printed some error message in such case and returned some custom, clearly defined exit code. Therefore, when developing a command-line C++ program, I like to catch and handle exceptions in the main function, like this:
#include <exception>
#include <cstdio>
enum PROGRAM_EXIT {
PROGRAM_EXIT_SUCCESS = 0,
PROGRAM_EXIT_FAILED = -1,
PROGRAM_EXIT_EXCEPTION = -2
};
int ActualProgram(int argc, char** argv) {
...
}
int main(int argc, char** argv) {
try {
return ActualProgram(argc, argv);
}
catch(const std::exception& ex) {
fprintf(stderr, "ERROR: %s\n", ex.what());
return PROGRAM_EXIT_EXCEPTION;
}
catch(...) {
fprintf(stderr, "UNKNOWN ERROR.\n");
return PROGRAM_EXIT_EXCEPTION;
}
}
Besides that, if you develop for Windows using Visual Studio, there is another, parallel system of throwing and catching exceptions, called Structured Exception Handling (SEH). It allows you to handle “system” errors that are not C++ exceptions and would otherwise terminate your program, even when using code shown above. This kind of error can be memory access violation (using null or incorrect pointer) or integer division by zero, among others. To catch them, you can use the following code:
#include <Windows.h>
int main(int argc, char** argv) {
__try {
return main2(argc, argv);
}
__except(EXCEPTION_EXECUTE_HANDLER) {
fprintf(stderr, "STRUCTURED EXCEPTION: 0x%08X.\n",
GetExceptionCode());
return PROGRAM_EXIT_EXCEPTION;
}
}
Few additional notes are needed here. First, SEH __try-__except section cannot exist in one function with C++ try-catch. It is fine, though, to call a function doing one way of error handling from a function doing the other one. Their order is important – C++ exceptions are also caught by SEH __except, but SEH exceptions are not caught by C++ catch. So, to do it properly, you need to make your main function doing SEH __try-__except, which calls some main2 function doing C++ try-catch, which calls ActualProgram – not the other way around.
If you wonder what are the process exit codes returned by default when exceptions are not caught, the answer can be found in the documentation of GetExceptionCode macro and Windows header files. When memory access violation occurs, this function (or the entire process, if SEH exceptions are not handled) returns -1073741819 = 0xC0000005, which matches EXCEPTION_ACCESS_VIOLATION. When a C++ exception is thrown, the code is -1073740791 = 0xC0000409, which is not one of EXCEPTION_ symbols, but I found it defined as STATUS_STACK_BUFFER_OVERRUN (strange…). Maybe it would be a good idea to extend the __except section shown above to decode known exception codes and print their string description.
Finally, you need to know that integer division by zero throws a SEH exception, but floating-point division by zero does not – at least not by default. There is EXCEPTION_FLT_DIVIDE_BY_ZERO and EXCEPTION_INT_DIVIDE_BY_ZERO error code defined, but the default behavior of incorrect floating-point calculations (e.g. division by zero, logarithm of a negative value) is to return special values like Not a Number (NaN) or infinity and proceed with further calculations. This behavior can be changed, as described in “Floating-Point Exceptions”.