4 Horrible C++ Pitfalls that everyone should know!

แชร์
ฝัง
  • เผยแพร่เมื่อ 28 พ.ย. 2024

ความคิดเห็น • 127

  • @furuthebat
    @furuthebat 4 หลายเดือนก่อน +51

    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  4 หลายเดือนก่อน +14

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

    • @nicholas_obert
      @nicholas_obert 3 หลายเดือนก่อน +5

      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 3 หลายเดือนก่อน +1

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

  • @Brad_Script
    @Brad_Script 3 หลายเดือนก่อน +33

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

    • @sebastianfalcone5046
      @sebastianfalcone5046 3 หลายเดือนก่อน +1

      This is the right solution to many of the issues

  • @armancheshmi7702
    @armancheshmi7702 4 หลายเดือนก่อน +49

    I like headbanging though

    • @mr.hashundredsofprivatepla3711
      @mr.hashundredsofprivatepla3711 4 หลายเดือนก่อน +6

      He isn’t talking about metal

    • @noxagonal
      @noxagonal 3 หลายเดือนก่อน

      @@mr.hashundredsofprivatepla3711 Ngl, C++ is pretty metal.

    • @caiocouto3450
      @caiocouto3450 3 หลายเดือนก่อน +2

      I do too but not while debugging

  • @sledgex9
    @sledgex9 3 หลายเดือนก่อน +15

    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  3 หลายเดือนก่อน +5

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

    • @sledgex9
      @sledgex9 3 หลายเดือนก่อน

      @@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 3 หลายเดือนก่อน

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

  • @vastabyss6496
    @vastabyss6496 3 หลายเดือนก่อน +3

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

  • @anon_y_mousse
    @anon_y_mousse 4 หลายเดือนก่อน +13

    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.

  • @AntonioNoack
    @AntonioNoack 4 หลายเดือนก่อน +10

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

  • @user-sb5vt8iy5q
    @user-sb5vt8iy5q 3 หลายเดือนก่อน +2

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

  • @loyc12
    @loyc12 3 หลายเดือนก่อน +5

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

    • @balijosu
      @balijosu 3 หลายเดือนก่อน +2

      Don't forget our good buddy -pedantic

  • @noritesc5000
    @noritesc5000 4 หลายเดือนก่อน +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

  • @balijosu
    @balijosu 3 หลายเดือนก่อน

    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.

  • @venilc
    @venilc 3 หลายเดือนก่อน

    "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++ 😅

  • @mito2453
    @mito2453 4 หลายเดือนก่อน

    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.

  • @RetroAndChill
    @RetroAndChill 3 หลายเดือนก่อน

    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

  • @RebelliousX
    @RebelliousX 3 หลายเดือนก่อน +1

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

  • @MilkywayWarrior1618
    @MilkywayWarrior1618 3 หลายเดือนก่อน

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

  • @artey6671
    @artey6671 2 หลายเดือนก่อน

    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.

  • @GhostVlVin
    @GhostVlVin 4 หลายเดือนก่อน +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. 4 หลายเดือนก่อน

      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  4 หลายเดือนก่อน

      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  4 หลายเดือนก่อน

      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 3 หลายเดือนก่อน

      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.

  • @dziuaftermidnight
    @dziuaftermidnight 3 หลายเดือนก่อน

    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.

  • @skeleton_craftGaming
    @skeleton_craftGaming 3 หลายเดือนก่อน +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 3 หลายเดือนก่อน

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

    • @skeleton_craftGaming
      @skeleton_craftGaming 3 หลายเดือนก่อน +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 3 หลายเดือนก่อน

    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,

  • @lassdasi
    @lassdasi 3 หลายเดือนก่อน +1

    clang-tidy to the rescue!

  • @jasiek1309
    @jasiek1309 3 หลายเดือนก่อน

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

  • @tuvshinbayarmandakh7035
    @tuvshinbayarmandakh7035 3 หลายเดือนก่อน

    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.

  • @bananacraft69
    @bananacraft69 4 หลายเดือนก่อน +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 4 หลายเดือนก่อน +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 4 หลายเดือนก่อน +2

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

    • @elijahshadbolt7334
      @elijahshadbolt7334 3 หลายเดือนก่อน

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

    • @bananacraft69
      @bananacraft69 3 หลายเดือนก่อน +1

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

    • @marcinwawrzkow7394
      @marcinwawrzkow7394 3 หลายเดือนก่อน

      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

  • @ltecheroffical
    @ltecheroffical 4 หลายเดือนก่อน

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

  • @kritomasP
    @kritomasP 3 หลายเดือนก่อน

    there's also -fsanitize=undefined for UB

  • @gtdcoder
    @gtdcoder 3 หลายเดือนก่อน +1

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

  • @sadscientisthououinkyouma1867
    @sadscientisthououinkyouma1867 4 หลายเดือนก่อน +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 4 หลายเดือนก่อน

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

    • @rubynaxela8524
      @rubynaxela8524 3 หลายเดือนก่อน

      goto

    • @dogyX3
      @dogyX3 3 หลายเดือนก่อน +5

      @@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 3 หลายเดือนก่อน

      The X-macro technique is very useful.

  • @panjak323
    @panjak323 3 หลายเดือนก่อน

    Why couldn't you use constexpr in your case ?

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  3 หลายเดือนก่อน

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

    • @panjak323
      @panjak323 3 หลายเดือนก่อน

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

  • @ChrisCarlos64
    @ChrisCarlos64 4 หลายเดือนก่อน

    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.

  • @waxlbloh6450
    @waxlbloh6450 3 หลายเดือนก่อน

    #1 using cpp

  • @moonyl5341
    @moonyl5341 4 หลายเดือนก่อน +1

    1:12 how is that done

    • @wowyomad
      @wowyomad 4 หลายเดือนก่อน

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

    • @AntonioNoack
      @AntonioNoack 4 หลายเดือนก่อน

      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 4 หลายเดือนก่อน

      @@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.

  • @sledgex9
    @sledgex9 3 หลายเดือนก่อน

    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  3 หลายเดือนก่อน +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 3 หลายเดือนก่อน

      @@lowlevelgamedev9330 oh, ok.

    • @sledgex9
      @sledgex9 3 หลายเดือนก่อน +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 3 หลายเดือนก่อน

      @@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 3 หลายเดือนก่อน

    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  3 หลายเดือนก่อน +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

  • @benshulz4179
    @benshulz4179 3 หลายเดือนก่อน

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

  • @xenopholis47
    @xenopholis47 4 หลายเดือนก่อน

    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  4 หลายเดือนก่อน +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 4 หลายเดือนก่อน

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

  • @atackhelikopter4303
    @atackhelikopter4303 3 หลายเดือนก่อน +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  3 หลายเดือนก่อน

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

    • @balijosu
      @balijosu 3 หลายเดือนก่อน

      Paste your problematic code here 😁

    • @atackhelikopter4303
      @atackhelikopter4303 3 หลายเดือนก่อน

      @@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 3 หลายเดือนก่อน

      @@atackhelikopter4303 I'm ready when you are 🙂

  • @martiqmarty
    @martiqmarty 3 หลายเดือนก่อน

    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 3 หลายเดือนก่อน +1

    the new keyword is pure evil

    • @benshulz4179
      @benshulz4179 3 หลายเดือนก่อน

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

  • @Dizintegrator
    @Dizintegrator 3 หลายเดือนก่อน

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

    • @K9Megahertz
      @K9Megahertz 3 หลายเดือนก่อน

      Yeah I was confused on this as well.

  • @marks_shot
    @marks_shot 4 หลายเดือนก่อน

    camaka

    • @arl-t8d
      @arl-t8d 4 หลายเดือนก่อน

      cameka

  • @discontinuity7526
    @discontinuity7526 3 หลายเดือนก่อน

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

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  3 หลายเดือนก่อน +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 3 หลายเดือนก่อน

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

  • @dampfwatze
    @dampfwatze 3 หลายเดือนก่อน +7

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

    • @dampfwatze
      @dampfwatze 3 หลายเดือนก่อน

      @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.

  • @thepuppetqueen57
    @thepuppetqueen57 3 หลายเดือนก่อน

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

    • @lowlevelgamedev9330
      @lowlevelgamedev9330  3 หลายเดือนก่อน

      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 3 หลายเดือนก่อน

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

  • @Mempler
    @Mempler 3 หลายเดือนก่อน

    Nr 1) C++

  • @downbad.
    @downbad. 4 หลายเดือนก่อน

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

  • @vycdev
    @vycdev 4 หลายเดือนก่อน +2

    first

  • @user-tw2kr6hg4r
    @user-tw2kr6hg4r 3 หลายเดือนก่อน

    1. Choosing C++ over any other language.

    • @benshulz4179
      @benshulz4179 3 หลายเดือนก่อน

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

  • @EduardKaresli
    @EduardKaresli 3 หลายเดือนก่อน

    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 3 หลายเดือนก่อน

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

    • @desnerger6346
      @desnerger6346 3 หลายเดือนก่อน

      @@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 3 หลายเดือนก่อน

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

    • @linsoe-u4d
      @linsoe-u4d 3 หลายเดือนก่อน

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

    • @linsoe-u4d
      @linsoe-u4d 3 หลายเดือนก่อน

      ​@@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.