4 Deadly C++ Pitfalls that you should know about.

Поделиться
HTML-код
  • Опубликовано: 12 сен 2024

Комментарии • 129

  • @furuthebat
    @furuthebat Месяц назад +49

    Always test with sanitizers, asserts and enable all the warnings 😎
    (I just can't understand why the compiler didn't throws an hard error if you forget to return a value...)

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +13

      it actually has, but sometimes it can't figure that out corectly.

    • @nicholas_obert
      @nicholas_obert 29 дней назад +2

      It really depends on the specific compiler and flags. Most modern compilers will add a return instruction even if you don't include it in the source code so that you can exit from the function. 'int main()' is a perfect example of the compiler automatically adding a return instruction on your behalf.
      But if you don't specify any return value, what does it return?
      Let's start by saying that there are multiple function calling conventions. The calling convention I use in the compilers I write (I don't remember if the C standard does it the same way, but it gotta be similar anyway) works as following:
      - make space on the stack for the return value by pushing the stack pointer by the static size of the return value type (the type of the return value must have a statically known size, otherwise you can return a pointer to a heap-allocated value. Pointers always have a static size). (This step is usually optimized by passing the return value through registers if the size is small enough to fit into a register).
      - push onto the stack the arguments to the function being called (usually this step is optimized by passing most arguments through registers).
      - push the current program counter onto the stack (so that the CPU can later jump to the instruction next to the function call when returning.
      - jump to the address of the function being called and execute its instructions
      On most architectures, the return instruction (sometimes called 'ret') just pops the topmost element off the stack, interprets it as the return address and jumps to it, effectively returning from where the function was called.
      Remember that the return value is either located on the stack or in a register, so it exists even if you don't specify any with a return statement in your source code. However, the return value is not automatically initialized and it's your responsibility as the programmer to do so correctly. C and C++, unlike safe Rust, do allow uninitialized values. Because of this, a function of the type 'int foo()' does not need to initialize the return value of type 'int'.
      That said, I think any compiler should complain about a function returning an unspecified value, except maybe for the 'int main()' function.

    • @ABaumstumpf
      @ABaumstumpf 14 дней назад

      @@nicholas_obert "But if you don't specify any return value, what does it return?" A compiler-Error.

  • @Brad_Script
    @Brad_Script Месяц назад +33

    that's why I enable 20+ warnings on top of -Wall and -Wextra in GCC

  • @armancheshmi7702
    @armancheshmi7702 Месяц назад +47

    I like headbanging though

  • @sledgex9
    @sledgex9 Месяц назад +14

    Also I swear I have seen compilers WARN you about forgetting to return from a non-void function. And also about forgetting to initialize a variable. The latter probably requires an increased warning level. Moral of the story: Always use the biggest warning level you can and always tell the compiler to treat warnings as errors.

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +5

      well in msvc those things will be reported as errors not warnings fortunatelly, I actually ignore warnings 😂

    • @sledgex9
      @sledgex9 Месяц назад

      @@lowlevelgamedev9330 I suspect that you get a lot of dumb warnings that's why you have been conditioned to ignore them. Try explicitly turning off the the dumb ones. I suspect those are very few, but produce a lot of noise. Also search StackOverflow on how to disable warnings on MSVC coming from external includes.

    • @balijosu
      @balijosu 29 дней назад

      I can never bring myself to ignore warnings. -Werror every time!

  • @anon_y_mousse
    @anon_y_mousse Месяц назад +12

    Some useful advice, since the order of static initialization of globals isn't defined, put them all in one source file and declare them extern in a header that every other module includes. If all of your globals are in one place and defined top-down, then you'll be fine. Just remember to minimize your usage of globals. Also, avoid codependent structs and classes. That's a sure way of getting weird bugs and compilation fails that you won't necessarily understand. I can't remember the last time I had a buffer overflow issue because for static arrays I have a macro I defined years ago that is in my "standard" header which simply does sizeof( array ) / sizeof( *array ) and I use a custom array type for dynamic arrays which includes a length member.

  • @loyc12
    @loyc12 Месяц назад +4

    Wait y’all know about -Wall -Werror -Wextra -Shadow compilation flags right? Right??

    • @balijosu
      @balijosu 29 дней назад +1

      Don't forget our good buddy -pedantic

  • @AntonioNoack
    @AntonioNoack Месяц назад +9

    Mmmh, you should have mentioned tools like Valgrind, which are runtime sanitizers.

  • @user-sb5vt8iy5q
    @user-sb5vt8iy5q Месяц назад +2

    Always use fsanitize address and fsanitize undefined behavior flags, Wpedantic also helps

  • @RebelliousX
    @RebelliousX 28 дней назад +1

    how can you forget to return a value to the function that asks for a return value? I believe the compiler will complain.

  • @vastabyss6496
    @vastabyss6496 29 дней назад +2

    I love the Minecraft music in the background. Nice video!

  • @venilc
    @venilc 23 дня назад

    "Never assume you know what the problem is"
    I dont know why, but I feel that. I've been surprised so often at what the actual error happened to be.
    I rarely say im 100% sure about something related to c++ 😅

  • @gtdcoder
    @gtdcoder Месяц назад +1

    Don't use raw pointers and manage resources with RAII.

  • @noritesc5000
    @noritesc5000 Месяц назад +2

    very fun bug i once got with stack coreption
    a example:
    void SetTo10(int& Value) { Value = 10; }
    char A;
    SetTo10((int&)A);
    then i learned don't cast values if funcion is expecting a refrence

  • @sadscientisthououinkyouma1867
    @sadscientisthououinkyouma1867 Месяц назад +9

    Macros are not evil, like anything called "evil" in programming it is because some people don't know how to use them correctly and cause far worse errors in the process.

    • @ohwow2074
      @ohwow2074 Месяц назад

      They're evil. Sure they have some obscure use cases though.

    • @rubynaxela8524
      @rubynaxela8524 Месяц назад

      goto

    • @dogyX3
      @dogyX3 Месяц назад +4

      @@rubynaxela8524 I think I've seen it used like a super-break in nested loops. Sometimes, its much neater than setting a flag, break, then checking that flag outside the inner loop

    • @balijosu
      @balijosu 29 дней назад

      The X-macro technique is very useful.

  • @balijosu
    @balijosu 29 дней назад

    Best to avoid static initialization entirely. It seems simple, but it can definitely cause you headaches down the line.
    Watchpoints are also worth mentioning. I.e. expressions the debugger watches for changes. Try to make them hardware watchpoints so they're fast.

  • @dziuaftermidnight
    @dziuaftermidnight 23 дня назад

    to me, it was simpler to just switch to C entirely. yes, you may have to write more code sometimes, but at least you can truly master it. and also, no worries about implicit stuff that C++ does all the time.

  • @mito2453
    @mito2453 Месяц назад

    An advice for dealing with undefined behavior or errors coming from different file/source code that is not part of the project. You can use the call stack to trace where the code was called from and go back until you find code that is part of your project. At least for me this helped me a lot when I got a random error from stbi_image and didn’t know what caused it.

  • @tuvshinbayarmandakh7035
    @tuvshinbayarmandakh7035 25 дней назад

    It has happened to me when doing CP. My code sometimes gave correct answers but sometimes it did not. I was like "What in the quantum is this?", the bug was indeed undefined behavior. I forgot to return the value from the function XD.

  • @artey6671
    @artey6671 10 дней назад

    Here's a strange thing I noticed: For ints a and b, the expression a + b + b ist not necessarily equivalent to a + 2 * b.

  • @bananacraft69
    @bananacraft69 Месяц назад +8

    macros can definitely be useful. i'm working on a project, where i have to access a specific value a lot, but "this->memory[this->registers[registerIndexes::ri_ISPT]]" is a mouthful so i just used a macro to reduce it to smth smaller, then undefined it at the end of the file

    • @felps3213
      @felps3213 Месяц назад +3

      In such cases I like to define a separate function for accessing the value. Compiler will inline calls to one-line functions 99.9% of the time in release mode (-O). Or even defining a lambda locally in the function where said value is accessed often.

    • @bananacraft69
      @bananacraft69 Месяц назад +2

      @@felps3213 yeah but i think macros are just simpler in this usecase tbh

    • @elijahshadbolt7334
      @elijahshadbolt7334 Месяц назад

      Be aware of simple macro names, you don't want to accidentally undefine someone else's library macro.

    • @bananacraft69
      @bananacraft69 Месяц назад +1

      @@elijahshadbolt7334 dw i always check if a macro name is in use before i do smth like that TwT

    • @marcinwawrzkow7394
      @marcinwawrzkow7394 Месяц назад

      It might be also good to define lambadas for accessing in single file or even method. If you’re using multiple object which share methods but don’t share interfaces the concept might be helpful.
      Even if the codebase gets a little bit bigger, you’re guaranteed to have Much cleaner error messages, than in macros, that are just copy and paste.
      At least imo

  • @RetroAndChill
    @RetroAndChill 26 дней назад

    As someone who programs Java for work and then uses C++ in my spare time, I do like how many more things the Java compiler outright forbids that C++ only lightly warns you about

  • @skeleton_craftGaming
    @skeleton_craftGaming Месяц назад +2

    2:34 which is part of the reason that it is bad practice to use a c style array.
    if you were following beat practices you would've been useing std::array[or std::vector]::at which throws if you try to access a out of bounds value

    • @redpepper74
      @redpepper74 Месяц назад

      Wait does indexing a std::array still let you read out of bounds?

    • @skeleton_craftGaming
      @skeleton_craftGaming Месяц назад +1

      @@redpepper74 I think it throws [If you have exceptions enabled of course]. I said at instead because it is granted to be a constant member function. Where as there are cases where the index overload isn't.

  • @Johnny-tw5pr
    @Johnny-tw5pr 20 дней назад

    One of the worst errors I've dealt with is when I opened a very large and old project that I had completely forgotten its existence. I try to compile and I get very ambiguous errors in the xmemory file. Boy that took a few days to debug,

  • @MilkywayWarrior1618
    @MilkywayWarrior1618 25 дней назад

    Forgetting to declare a destructor as virtual is one of my (least) favourite

  • @lassdasi
    @lassdasi Месяц назад +1

    clang-tidy to the rescue!

  • @jasiek1309
    @jasiek1309 24 дня назад

    Remember that a++ + a++ is undefined behavior

  • @GhostVlVin
    @GhostVlVin Месяц назад +1

    In one of previous videos, you told us, that you allocate memory as less as possible.
    In game, you have game loop where you update and draw all your objects, and I sure that you couple them in one vector or list using some common interface, so I have a question, how are you using vector of Interface based objects without memory allocation in cpp, or if you are not, what other technics you are using to get this result

    • @Sebo.
      @Sebo. Месяц назад

      the signature of std::vector is
      template
      class vector;
      this templated argument Allocator manages how memory is allocated for the vector, you can for example write your own allocator so that it manages a pool so that instead of calling new, malloc or something else to get more memory from system, you call it once but large enough to store everything and you manage it yourself, i.e. construct or destruct other objects, this provides a speedup because you don't have to ask system for memory every time or you can free everything when you exit (which you can also do with the default allocator); this is just one way and is in my opinion the cleanest because it can be made generic

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад

      yo so, you can't get away with not allocating memory ever. But the idea is to allocate only when needed. So for growing arrays, I use vectors because they are conviniend and they clear the memory when I exit the scope. To answer your question, you can watch that long one hour video about my minecraf clone, but basically I have ome vector for each entity type and I have some tricks to iterate through all of them. The idea is that if no entities are created, there is no memory allocated so the game moves fast.

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад

      so you can use the polimorfic allocator verison, for the normal vector you can indeed pass an allocator but the api for that is very wierd and has a very wierd limitation, so it is like it doesn'f even exist unofortunatelly :(((
      What I would do is I would add a temporary arena if needed. Like one that clears every frame

    • @insentia8424
      @insentia8424 Месяц назад

      What I do in the game I'm writing from scratch:
      I allocate a chunk of virtual memory and then use that. The OS and hardware then map that memory as it's needed into physical memory. This means I could even request unreasonably huge amounts of memory for a game that just get more entities the longer you paly that can't get unloaded (automation/factory builders).
      This means manually taking care of that memory and using/reusing it while the game runs, but it also means the only real point of failure in regards to allocations is in the beginning.
      There might be slow downs related to the memory mapping I am not privy to yet. And it'd be an interesting thing to see if it's worse than memory allocations or not, but that's for future me to discover.

  • @ltecheroffical
    @ltecheroffical Месяц назад

    How do you orginize your games? I mean how are you orginizing your objects, scenes, dialog and more?

  • @kritomasP
    @kritomasP Месяц назад

    there's also -fsanitize=undefined for UB

  • @ChrisCarlos64
    @ChrisCarlos64 Месяц назад

    Macros are not evil but they are easily abused and can be obtuse on how to use them.
    I will only use them as a last resort in some cases and maybe to cut down on repetitive stuff. Otherwise, I try to avoid them when possible because I hate the pain of leaked macros.

  • @Antagon666
    @Antagon666 Месяц назад

    Why couldn't you use constexpr in your case ?

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад

      long story, glm didn't let me for some reason, no idea why honestly, it gave me compiler errors

    • @Antagon666
      @Antagon666 Месяц назад

      @@lowlevelgamedev9330 yeah right, it probably doesn't have constexpr constructors.

  • @moonyl5341
    @moonyl5341 Месяц назад +1

    1:12 how is that done

    • @wowyomad
      @wowyomad Месяц назад

      getData() returns a reference to a local variable. It's an undefined behaviour. Usually you would get segmentation fault here.

    • @AntonioNoack
      @AntonioNoack Месяц назад

      You have to watch a bit further, where you then can see the getData function.
      int data = 10; is set inside the function getData(), but int data outside the function is using the return value of getData(), and that is undefined.

    • @wowyomad
      @wowyomad Месяц назад

      @@AntonioNoack I replied here before your comment but the reply is gone😕.
      I'm too lazy to rewrite it but in short, it's not just because the variable was created inside the function but also because he returned a reference to it, not the value itself. And since value was created on the stack, it got erased before return was called.

  • @benshulz4179
    @benshulz4179 27 дней назад

    0:58 none of these examples even compile, lol.

  • @waxlbloh6450
    @waxlbloh6450 17 дней назад

    #1 using cpp

  • @atackhelikopter4303
    @atackhelikopter4303 Месяц назад +1

    sometimes warnings are good, other times the compiler doesn't like your code and you get 20+ warnings, and it is unfixable (nothing i tried to to fix them worked and nothing i tried to make the code unstable worked)
    that's my experience with warnings lol

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад

      yes lol, that's why I ignore them 😂😂

    • @balijosu
      @balijosu 29 дней назад

      Paste your problematic code here 😁

    • @atackhelikopter4303
      @atackhelikopter4303 29 дней назад

      @@balijosu 600 lines of code, that's way to many bruv
      i would need to make a pastebin and search for the code because it is months old

    • @balijosu
      @balijosu 29 дней назад

      @@atackhelikopter4303 I'm ready when you are 🙂

  • @sledgex9
    @sledgex9 Месяц назад

    You probably meant "buff[32]" instead of "buff[33]". Using 33 means it is 2 elements past the end. The valid range of your array is 0-31.

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +1

      actually I meant 2 elements, it seems like the last element was uses as a guard or something in debug to check for this kind of overflows

    • @sledgex9
      @sledgex9 Месяц назад

      @@lowlevelgamedev9330 oh, ok.

    • @sledgex9
      @sledgex9 Месяц назад +1

      @@lowlevelgamedev9330 After you comment I did some digging on StackOverflow. It is legal C++ to point to one element past the end. Dereferencing it is undefined behavior. This can be used as a marker for the end. Think of it in the context of STL containers. All the end() iterators of stl containers are defined as "pointing to one element past the end". Hence "return array+arraysize;" is a valid implementation for and end() method.

    • @sledgex9
      @sledgex9 Месяц назад

      @@lowlevelgamedev9330 The last bit I forgot. By allowing one-past-end element to be valid, that probably means that the next item on the stack (aka the next declared int variable) would be 2 elements past the end. So your buff[33] works on corrupting the other variable.

  • @zanagi
    @zanagi Месяц назад

    How do you have your c++ game published on steam? I thought they only allow unity or unreal unless you contact them i guess

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +4

      what? no, also like half of the games on steam use custom engines, tho don't take my word for it, I am just speaking out of memory, there are statistics online, you can use whatever you want they don't care

  • @xenopholis47
    @xenopholis47 Месяц назад

    so when he throws arrows and the particles come out after arrows hit are constructors? and does he use destructors after it so that gets removed? am i right to understand that?

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +1

      basically the colors are static variables. They are constructed once when the program starts, and they have that value for the whole program. I just use the color values. But they are wrong because they were initialized wrong when the program started

    • @xenopholis47
      @xenopholis47 Месяц назад

      @@lowlevelgamedev9330 Oooooohhhhh.... ok ok got it. I had to read it 5 times to get it LMAO

  • @martiqmarty
    @martiqmarty Месяц назад

    i like your videos, but find it so hard to follow as a non native english speaker aswell. if you would talk a little slower i think it would be much easier to follow. The content is so good so i find it frustrating i cant understand sometimes

  • @billclinton4913
    @billclinton4913 Месяц назад +1

    the new keyword is pure evil

    • @benshulz4179
      @benshulz4179 27 дней назад

      tell me you've never coded without telling me you've never coded

  • @marks_shot
    @marks_shot Месяц назад

    camaka

  • @Mempler
    @Mempler Месяц назад

    Nr 1) C++

  • @discontinuity7526
    @discontinuity7526 Месяц назад

    hey Low Level Game Dev, what do you think about using rust?

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад +2

      yo, its not really for me and I don't think it fits gamedev. I preffer to be more flexible while writing code, and the safety that rust gives I can get from the way I structure my code. So it isn't really for me. I don't even use const in my code for context lol 😂😂

    • @discontinuity7526
      @discontinuity7526 Месяц назад

      @@lowlevelgamedev9330 lol makes sense, thanks for the reply 😂

  • @dampfwatze
    @dampfwatze Месяц назад +7

    When you realize that Rust's main feature is to prevent undefined behavior... 🦀

    • @marcsfeh
      @marcsfeh Месяц назад +3

      it isn't. You can simply define or delegate the definition of so called "undefined" behavior, this is a issue with how C/C++ are standardized, there's no real "undefined" behavior for a CPU under normal conditions. Rust's main selling point is checking for memory errors with its substructural type system

    • @dampfwatze
      @dampfwatze Месяц назад

      @@marcsfeh This not about the specific execution model of a CPU. While a computation platform is generally deterministic, the exact state of a system is usually not reoccurring. Most undefined behavior comes from access to where it shouldn't be/it was not expected, or race conditions. Race conditions depend on many factors in the OS, that are different every time. Rust eliminates undefined behavior mainly through the borrow checker, which ensures every possible access to data is known and can be handled. It also provides many well engineered constructs to deal with uncertain situations at runtime. All this stems from Rust's strict policy against undefined behavior. Memory safety is one part of this. Usually, Memory safety can be advertised better.

    • @marcsfeh
      @marcsfeh Месяц назад

      ​@@dampfwatze You are conflating 2 distinct things here, "Undefined behavior" is a standardization problem, knowing the runtime state of the CPU is another thing. C and C++ are poorly standardized and use Undefined Behavior as way to make the compiler "non liable" for optimizations which kill program correcteness. C wasn't like that originally, but became this UB monster because many things which were preivously "ISA defined", or "OS defined", became "undefined" due to design by committee. Rust is not particularly special in this regard, without its borrow checker it's basically C++ mixed with OCaml with a much saner type system. And rust does not have a strict policy (or at least, doesn't follow such policy) against undefined behavior, in fact, rust if *full* of UB in its unsafe mode, this is very different from languages like Zig or Odin which even though have no borrowing mechanisms, tackle a lot of undefined behavior, odin in particular does that very well by simply standardizing what C and Pascal programmers already expect

  • @thepuppetqueen57
    @thepuppetqueen57 Месяц назад

    I love c++ I really do but most libraries make me wanna die

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  Месяц назад

      well most cpp devs use their own libraries for this reason, so maybe give it a try to make your own library, you will like it 💪

    • @thepuppetqueen57
      @thepuppetqueen57 Месяц назад

      @@lowlevelgamedev9330 nah the libraries I use are server libraries and I would rather die than make one of those all by myself

  • @Dizintegrator
    @Dizintegrator Месяц назад

    Bro writes c99 and presents it as c++ problems bruh

    • @K9Megahertz
      @K9Megahertz Месяц назад

      Yeah I was confused on this as well.

  • @downbad.
    @downbad. Месяц назад

    Reminder to keep knives and ropes away while using *C++*

  • @vycdev
    @vycdev Месяц назад +2

    first

  • @user-tw2kr6hg4r
    @user-tw2kr6hg4r Месяц назад

    1. Choosing C++ over any other language.

    • @benshulz4179
      @benshulz4179 27 дней назад

      what's the problem with C++ in game engine? One of the best languages to use.

  • @EduardKaresli
    @EduardKaresli Месяц назад

    Avoid all these problems by moving to Rust. 🤷
    EDIT: since my answer to the guy below wasn't published, I have to assume that the author of this channel doesn't like when facts are not in his favor.
    Hence I will state here that: 1. there is an operating system written in Rust and is called Redox OS.
    2. That Rust is used in the Linux kernel itself.
    3. Rust is used in gamedev and there are game engines built with Rust, for example the Bevy game engine.
    4. After programming for 20+ years (15+ in C++, 7+ in JS/TS) I can safely state that, for C++ developers, there are absolutely no excuses why NOT move to Rust.

    • @averdadeeumaso4003
      @averdadeeumaso4003 Месяц назад

      Operating Systems APIs don't use Rust, low level devs must use C and C++

    • @desnerger6346
      @desnerger6346 Месяц назад

      @@averdadeeumaso4003 ​safe != high-level. Just because someone uses e.g. winit/glutin instead of glfw, or even glium instead of raw opengl calls, that doesn't mean the person isn't a low-level game developer. The safe API wrappers are a pretty thin layer in the overall game engine architecture.

    • @EduardKaresli
      @EduardKaresli Месяц назад

      @@averdadeeumaso4003 There is an operating system written in Rust called Redox OS. Also, Rust is used in the Linux kernel.

    • @user-ed1nw6vr8n
      @user-ed1nw6vr8n Месяц назад

      Tbh, rust makes game dev even more complicated and don't give advantages

    • @user-ed1nw6vr8n
      @user-ed1nw6vr8n Месяц назад

      ​@@averdadeeumaso4003​ I don't exactly get what you mean, but those low level apis can be used from rust and also alot of windows componets are being rewrite in rust. rust is also now part of the linux kernel.