The What, How, and Why of Void Pointers in C and C++?

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

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

  • @SimGunther
    @SimGunther 2 года назад +12

    Hearing that this is "a pointer VOID of any type information" was so enlightening.

  • @wick6296
    @wick6296 2 года назад +5

    When a friend asked me for a youtube channel that explains topics regarding C/C++, you were the first one that came in my mind. Keep up the good job!

  • @HansBezemer
    @HansBezemer 2 года назад +40

    Although I tend to HATE C++, I do agree with being explicit. It shows intentionality. No, I didn't forget - I really wanted this to happen. It also allows for catching errors if you change types.
    On the other hand - the "void" type is badly chosen (like many keywords in C, e.g. "static" for functions). It would have been a LOT better if void would have been expressed in "address units". It would also have made sense of "void pointer arithmetic".

    • @sanderbos4243
      @sanderbos4243 2 года назад +10

      It would've also been clearer to me much sooner if there was a more significant syntax difference between the "void" returned by a function and the "void *" type. If it were called "something *" or "thing *" it would've been a lot more obvious to me that "void" and "void *" are pretty different.
      Also, the fact that a "void *" can't be dereferenced while a "void **" can is something that took me a while to wrap my head around. I guess it is because when dereferencing a "void *" the compiler doesn't know how many bytes it should read since there is no type to tell it how many bytes to read, while when dereferencing a "void **" it knows how many bytes it should read, since pointers are all the same size.

    • @HansBezemer
      @HansBezemer 2 года назад +5

      @@sanderbos4243 In the old days ALL functions basically returned "int". You could ignore it if you wanted, but still. Nowadays I can't "convince" GCC to do something with an explicit void return type. But this one still does the job (although the compiler complains loudly):
      #include
      test () { int a = 45; return (a); }
      int main (int argc, char **argv)
      {
      printf ("%d
      ", test());
      }
      Even replacing this one does the job (returning zero):
      test () { int a = 45; }

    • @sanderbos4243
      @sanderbos4243 2 года назад +1

      @@HansBezemer Wow, thanks for sharing!

    • @reinasama904
      @reinasama904 Год назад

      @@HansBezemer This is very interesting! thanks for sharing

    • @RobBCactive
      @RobBCactive 11 месяцев назад +1

      ​@@sanderbos4243firstly when void & void* were defined there was a lot of C written already, adding keywords to languages breaks programs, so needs a strong justification and the over loading reduced the impact.
      Before void* char* served but routines like malloc(3) would actually allocate a pointer aligned for any type.
      It wasn't confusing to C programmers precisely because void wasn't a type.
      In C you shouldn't cast void* when using it, it's actually an artefact of C++'s stricter type rules that force it and that can be a maintenance burden. Casts are NOT desirable.

  • @ajithanandhan7009
    @ajithanandhan7009 2 года назад +1

    Thank god i found you. your videos are life saver,your so great. im a 24 year old Embedded software developer from india .happy coding

  • @cps7257
    @cps7257 2 года назад

    Usually do not click the like button but this really helped me

  • @DeniseNepraunig
    @DeniseNepraunig Год назад

    I haven't looked into void pointers until I watched your video. Just hearing the name "void pointer" always confused me because of my immediate thought void = nothing. But from your video I learned that I should think of it as "no type information" and that makes total sense now. Thanks a lot! I enjoy your channel A LOT!

  • @cleightthejw2202
    @cleightthejw2202 2 года назад

    @Jacob
    Thank!
    you!
    You're audio is way better today.

  • @khomo12
    @khomo12 Год назад

    Thank you!👍👍👍

  • @andrewnorris5415
    @andrewnorris5415 2 года назад +4

    You can use it to code a form of dynamic programming. A void pointer could point to one of several different types, which type it is being set elsewhere in another var. (Often within the item pointed to).

    • @stoneman210
      @stoneman210 Год назад

      auto keyword exists

    • @chair547
      @chair547 Год назад +3

      ​​@@stoneman210 that's not what the auto keyword is for. Auto is for code that has a single specific type that you don't want to write out. Void* allows you to make a function take multiple types

  • @ahmadhadwan
    @ahmadhadwan 2 года назад +16

    The best way to write C++ is to start each file with extern "C" {

    • @v01d_r34l1ty
      @v01d_r34l1ty 2 года назад +6

      TIL you can write C applications that use C++ APIs by simply making a C wrapper using extern "C" { ... } and compiling the .c files to .o using GCC, compiling the .cpp files to .o using G++, and linking the .o's together using G++. That's actually awesome, I thought it was only a one-way compatibility C->C++.

  • @mariovrpereira
    @mariovrpereira 2 года назад

    Your content is really good! thank you so much

  • @anon_y_mousse
    @anon_y_mousse 2 года назад +1

    One thing I keep seeing is people saying that void* isn't necessary anymore because of any non-C language feature, but clearly they've forgotten that old systems still exist that need their code maintained and new systems can benefit from a good programmer using C as opposed to using whatever bloated feature their language of choice implements. Optimization is a lot easier for a compiler to do if each instruction you type in correlates to just a few machine instructions, or in some cases one. If you add a bunch of fluff, it gets harder for the compiler to determine how best to optimize your code, and makes the compiler slower, and its output more bloated.

  • @andrewporter1868
    @andrewporter1868 2 года назад +11

    I already knew all of this; I just like your videos.
    When are you going to do a video on the worst parts of C that shouldn't exist: undefined behavior, and unspecified behavior?

    • @REALsnstruthers
      @REALsnstruthers 2 года назад +10

      As annoying as undefined behavior can be, its pretty much the main reason as to why c ended up being such a fast compiled language. many architectures, especially back when c was first invented and still somewhat today, do not have consistent behavior. undefined behavior allows compilers to pick the best option it can for the architecture being compiled down to. Combined with how it took nearly 2 decades for the language to be standardized, meaning plenty of people were able to prod into every nook, cranny, and edge case the language could ever have, and its unsurprising how undefined behavior ended up being the way it is. I can admit that nowadays, in modern compilers, the fact they assume undefined behavior just doesnt happen by default and optimize assuming that such behavior is impossible can introduce a lot of annoying, subtle bugs.

    • @JacobSorber
      @JacobSorber  2 года назад +5

      Good question. Some day. Not sure which one. Are there specific undefined behaviors that you want to make sure I include - whenever that video is born?

    • @AGBuzz182
      @AGBuzz182 2 года назад +1

      @@REALsnstruthers you're right, but "behavior defined by the underlying hardware" would have been more useful than "undefined behavior", since it would have stopped standards-compliant compilers from breaking code that according to all reason should work!

    • @andrewporter1868
      @andrewporter1868 2 года назад +1

      @@JacobSorber None in particular, though mentioning GCC extensions and intrinsics would be worthwhile. Oh, actually, one that comes to mind is function pointer to state casts and vice versa. That's one of the more important ones.

    • @andrewporter1868
      @andrewporter1868 2 года назад +2

      @@REALsnstruthers Undefined behavior doesn't need to exist. You can just define an abstract machine upon which all things are defined. That's in fact something that I'm doing with a certain project of mine. That a particular function within that machine be a blackbox (as all models reduce to blackboxes due to the subjective nature of models) isn't a problem, but undefined behavior is. If behavior isn't defined, neither the design nor the implementation are certain in which case your code is useless without reading every compiler's documentation (or forcing everyone to use GCC semantics like I do), or avoiding UB altogether (something virtually impossible which I learned very early on trying to make everything strictly conforming to ISO C). That said, GNU C extensions master race.
      While I'm at it, the other major flaw with C concerns its design as a language. It lacks sufficient constructs for decoupling state interface from state implementation, i.e. structs are strictly state implementation. I can't define an unordered list of struct members of a particular amount of information in bits (no strictly power of two) and then elsewhere, define that struct in a way that is optimal for my needs on a particular architecture like x86 or ARM.
      Likewise, it lacks sufficient constructs to decouple behavior design from behavior implementation, i.e. logic programming. I can't represent my program succinctly as a logical circuit in the form of code, or my algorithm as just an algorithm instead of an implementation of an algorithm.
      Pointers are great, but I don't like how C goes about their semantics. They're too much of a black box. I should be able to just treat it as a numerical value if I need it to, nor should its data be strictly of a logarithmic size. Being able to have relative pointers or simply offsets that I can use as pointers are something sorely missing. Now, it is possible to create relative pointers yourself, but as it lacks the syntax necessary to make it pretty, it could make code unreadable because of its semantics (*relptr would be syntactic sugar for *(base+offset) or base[offset]). I am effectively saying that pointer compression should be a built-in feature, and that these compressed pointers existing as base and offset should be part of the language or stdlib.h by default. What's better? One 64-bit pointer with 256 8-bit offsets, or 256 64-bit pointers?
      Note, of course, that I'm well aware pointers themselves already act as memory contexts and that you can have offsets into this according to the amount of memory associated with the pointer, however, the issue with heap functions is that they tend to be expensive in implementation. This is yet another reason why an abstract machine would fix this. Since memory can be perfectly modeled, an ideal memory allocator design which compilers and platforms are free to implement as they please can be designed such that the slightly-more-than-naive implementation is cheap requiring very little metadata overhead. If such were done, then I could just simply reserve memory as memory without strictly worrying if it's on the heap or the stack, or, generally, what kind of storage class it is. There would be one storage class only, and the compiler can optimally determine the implementation details with very little help from the programmer such as whether the data is on the stack or on the heap on a particular platform; however, at the same time, there aren't officially supported constructs for speaking directly with the platform nor the hardware except through extensions (such as ASM extensions from GCC, or introducing coupling by adding a platform header). Sometimes, it's unavoidable, but it can be minimized to a great extent. How many ways are there, after all, to manipulate state, and how many behaviors? Is there anything but transient or persistent state with lifetimes varying subjectively according to that state's purpose?
      Also, where's me asynchronous parallelism? Get rid of threads and synchronous parallelism; just let me have instruction (or function) stacks and define my own master-slave scheduling system. I could be getting 64 operations per cycle in certain cases provided the overhead of synchronous schedulers adding tens to hundreds of cycles worth of overhead to my high performance, highly optimized implementations.

  • @KuroiMeansBlack
    @KuroiMeansBlack 5 месяцев назад

    Is It Better to use template instead of void pointer in c++?

  • @knofi7052
    @knofi7052 Год назад

    That's really a funny one, with 'feed beef', 'coffee' and 'dead beef'...😂

  • @FadyMegally
    @FadyMegally 2 года назад +1

    Would you please explain why ((uint16_t*)p)++ is not allowed ? uint16_t being just a stand-in for any type. I have a mem block that i wanted to fill with various types (uint16 followed by uin32 etc.) and instead of doing many p + sizeof(x) statements i was trying to be concise by using this *(((uint16_t*)p)++) = 0xC0FF ; *(((uint32_t*)p)++) = 0xAAC0FFEE; but that's forbidden apparently.

  • @marwankhaledkasem9335
    @marwankhaledkasem9335 2 года назад

    thank you👍

  • @TaleTN
    @TaleTN 2 года назад +1

    printf("%p") seems useful, thanks! I always use something like "%016llX", but then I have to know the size, cast to some large enough int type, etc. The only downside with "%p" is that the output seems to differ between compilers, but for quick debugging I guess this doesn't really matter.

    • @taragnor
      @taragnor Год назад

      Well honestly you'd always be using %p for debugging anyway. Printing memory addresses is never something you'd want to show the end user.

  • @Mantorp86
    @Mantorp86 2 года назад

    Great video!

  • @NikolaNevenov86
    @NikolaNevenov86 2 года назад +4

    so...technically a void pointer is a pure memory pointer? It doesn't know or care, how it should read the data at that memory address. While a type pointer will try to interpret the memory as a specific type?

    • @АлександрГольдварг
      @АлександрГольдварг 2 года назад +2

      Pretty much yes, but a pointer will always be interpreted as pointer in runtime until you try to dereference it. For example GCC does not allow to dereference void pointers, you have to cast it to some typed pointer.

  • @jose6183
    @jose6183 2 года назад

    I actually like both C and C++, I understand their use cases. I'm not pedantic like other people who love putting down the older brother, just because he's old.

  • @antonwalters4857
    @antonwalters4857 2 года назад +1

    Would have been nice to touch on Variant return types, used to return one or more different types from a function. Other than a generic pointer I believe variant types are the best use for void*.

  • @rustycherkas8229
    @rustycherkas8229 2 года назад

    "void *" made possible the generic implementation of qsort() that needs the facility of a comparison function that can only be provided with two generic memory addresses.
    It's one thing to "compare apples with apples", and quite another thing to compare oranges with oranges.

    • @rustycherkas8229
      @rustycherkas8229 2 года назад

      @@richardlyman2961 Not all arrays to be qsort()'d are arrays of ints...

    • @rustycherkas8229
      @rustycherkas8229 2 года назад +1

      @@richardlyman2961 What can I say? By extension, there's no need for 'const', either. We could even revert to Assembler and free ourselves of so many constraints...
      Have a nice day...

  • @erbenton07
    @erbenton07 Год назад

    Hey, what editor are you using?

  • @SpamTheHorse
    @SpamTheHorse 2 года назад +4

    I find void* interesting such as in an event callback system.
    You can make a function pointer such as
    (In c with using the "typedef" keyword) typedef void (*FunctionListener) (void*);
    or (In c++ with the "using" keyword) using FunctionListener=void(*)(void*);
    Then you can pass a void function with the void* argument that will be casted into any DataType* within that function, where that same DataType* has been passed into that function.
    You can also do something like void read_bytes(void* any_data, uintptr_t size); just to read bytes of any struct/class/datatype.

    • @sayori3939
      @sayori3939 8 месяцев назад

      Hehe that's exactly what I've done with a linked list library i made

  • @pierreabbat6157
    @pierreabbat6157 2 года назад +1

    Could you talk about intptr_t? I have an application (Bezitopo) where an edge can be split in three, and I need to point to the three pieces, so I took an edge pointer, converted it to intptr_t, and put the piece number in the bottom two bits, which are always 0 if the word size is at least 32 bits.

  • @majinuub619
    @majinuub619 2 года назад

    Wait did you just cast a left hand operand? didn't know we can do this!

  • @69k_gold
    @69k_gold Год назад +1

    Thinks like void pointers and function pointers are proof that C is not low-level or mid-level as most people call it

    • @d.sherman8563
      @d.sherman8563 Год назад

      You could argue that typed pointers are higher level, a void pointer is nothing more than a memory address, no size information or anything. It is purely up to you, the programmer, to use it correctly, the compiler can’t help you.

  • @janPolijan
    @janPolijan Год назад

    As you're speaking of void in this video, you ought to add void when your functions don't take arguments, so `void hello(void)` instead of `void hello()`. Those two don't mean the same thing (at least in C).

  • @user-lv6qm3fj2z
    @user-lv6qm3fj2z 2 года назад +7

    I was clicking all over my screen and here I am

    • @JacobSorber
      @JacobSorber  2 года назад +4

      Welcome. I hope you enjoyed where your random clicking landed you.

  • @dexter117
    @dexter117 2 года назад +2

    If you're in C++ use templates instead of void pointers in order to structure your code by using better practical techniques

  • @xbz24
    @xbz24 Год назад

    May I ask how your vscode has the same font as msvs and what font is it!

    • @JacobSorber
      @JacobSorber  Год назад

      You can change the vscode font in preferences. Google("What font does MSVS use by default?") --> "Cascadia Code"

  • @archibald-yc5le
    @archibald-yc5le 2 года назад +1

    For me it's more difficult in C++, where void* is frowned upon. Right now I'm doing an implementation of vector, currently working on range insertion. This operation requires some low-level copying, so I thought using std::memmove would be justified here. Is this a correct assumption? When is it generally appropriate to use some c-style expressions in C++, are there rules?

    • @noxagonal
      @noxagonal 2 года назад +2

      void* has many problems in C++ but if you know the type it is supposed to be, do feel free to use it if you need it. Technically you're only allowed to cast it back to the type it was originally and not to the inherited parent, though if you stick with single inheritance (EDIT: AND don't use virtual parents or member functions), you shouldn't have any problems casting void* object to it's parent type.
      C-style cast is not recommended because it's "general" cast. It's like static_cast const_cast and reinterpret_cast combined. Using C++ style casts are more clear on intent, I'd recommend using static_cast as it does compile time check, C-style cast does not. C-style cast also does not do dynamic_cast which makes it unsafe to use with virtual classes.

    • @chri-k
      @chri-k 2 года назад

      @@noxagonal what do you mean by ‘does compile time check - C-style cast does not’???
      ( i know practically nothing about “correct” C++ )

    • @noxagonal
      @noxagonal 2 года назад +2

      @@chri-k Hey. So... You can cast a type to another using 4 different keywords, each keyword only allows certain types of casts. I'll try to summarize the best I can, I apologize for the wall of text... :P
      C-style cast has no safeguards, anything can be cast to anything without any limitations. If you change your types somewhere, the C++ style casts may help you catch a potentially hard to debug bug in your application. These are more important in C++ as it has templates and auto variables, which means that you might not see the final types you're casting. It's less of a problem for C.
      static_cast: Compile time evaluation. What it considers an error is casting away const, casting from incompatible types (ie. types that are not inherited from each other), casting from incompatible number of pointer indirection levels. What it's meant for is casting base class to inherited class (non-virtual classes), casting void* to actual type and casting simple stuff like int to float... Though you don't really have to do that last one.
      const_cast: Compile time evaluation. Only allows casting away the const, does not allow changing of the type. Useful when you need to ensure the type stays the same.
      reinterpret_cast: Compile time evaluation. Cast any pointer type to any pointer type without checking anything, this means that it will not adjust pointer value to point to the right base class location when dealing with inheritance, which is something that the other casts do. Cannot cast away const. Useful when you need to keep the exact memory location. Which is actually something C-style cast in C++ does not always do.
      dynamic_cast: This is a little more specialized, it does a runtime check to see if casting is possible or not, this is only meant to deal with situations where you only have the base pointer and you're not sure what the original type was, this allows you to check it.
      struct A { virtual Function(); };
      struct B : A { virtual Function(); };
      struct C : A { virtual Function(); };
      B and C inherit from A. Lets assume:
      A * obj = new B();
      You'll notice that the B object now lives in memory pointed by A* only. You can try and cast it to either B* or C* like this:
      B * b_obj = dynamic_cast(obj); // This will succeed and you now have a handle to original type.
      C * c_obj = dynamic_cast(obj); // This will result "c_obj" to be null, because "obj" is not type C.
      (do not use static_cast for this)
      This is useful if you want to store multiple different types of objects within a single array or types created in an external library and you don't know the actual type. You usually would keep track of the type anyhow but this serves as an extra layer of protection.

    • @chri-k
      @chri-k 2 года назад

      @@noxagonal
      i have made a wall of text to counter your wall of text:
      your reply only made it more confusing:
      casts in C protect against incompatible types, and as you can see from the following example ( unless i am missing something very important ) the C-style C++ casts do too:
      class A {}; class B {};
      template A f(T a)
      {
      return (A)a; // [2]
      }
      int main() {
      f(B{}); // [1]
      return 0;
      }
      In instantiation of 'A f(T) [with T = B]':
      required from here
      error: no matching function for call to 'A::A(B&)'
      [ the giant but useful list of notes that happen on a C++ compilation error ]
      so it seems that the only differences between it and 'static_cast' is that it sees casting from const to non-const an error, but why would that matter if a cast returns an rvalue? rvalues are not modifiable anyway; and that it sees casting from a pointer to a pointer an error when the "level of indirection" is not the same.
      'const_cast' goes into the same category as 'static_cast' -- the "why does this exist i am definetly missing something very important" category, for the same reason -- you cannot modify an rvalue and, if you take the addess of a constant and drop the const off of the pointer, writing to it could cause a crash if the constant is compile-time ( and you have no way of knowing that if it is a parameter ).
      'reinterpret_cast' sounds like a very useful thing for when you need to access the bits of a float or something like that. in C you need an additional variable or a union.
      'dynamic_cast' and the explanation you provided with it went right over my head. i do not understand how such a situation -- where a pointer is pointing to a structure that you did not expect to be there -- can happen by any means other than a bug. that also goes into my evergrowing "why does this exist i am definetly missing something very important" section of C++ knowledge.
      so i am definetly missing something very important

    • @noxagonal
      @noxagonal 2 года назад

      @@chri-k I made a reply with a link to compiler explorer with some examples... seems like RUclips ate my reply. XP
      I'll see if I can dig out that link somewhere.

  • @goodguy7883
    @goodguy7883 2 года назад

    Can you please do a video on char pointer and const char pointer and their weird behaviour when passed in functions etc.

    • @JacobSorber
      @JacobSorber  2 года назад

      Probably. Can you clarify which "weird" behavior you're most interested in?

  • @kristaqvin
    @kristaqvin 2 года назад

    Nice vid

  • @noxagonal
    @noxagonal 2 года назад

    I like your videos, pretty informative.
    Also I wanted to point out that in C++, technically the only valid cast from a void pointer is back to the original type. If an object has inheritance (EDIT: OR uses virtual base classes), you think that you might be able to skip a cast somewhere by casting the void to a base class object. This is possible for sure but only if you cast to the first inherited class. If you use multiple inheritance and cast void pointer to the second inherited class of the object, then you will not get the correct memory location to that base class. Easy fix for this is to stick with single inheritance only and you shouldn't have any problems.

    • @anon_y_mousse
      @anon_y_mousse 2 года назад +2

      Or stick with C and write your "inheritance" code however you want.

    • @noxagonal
      @noxagonal 2 года назад

      ​@@anon_y_mousse It's logically impossible without abstracting this problem.
      The point is, inheritance is useful, and readily available. It's just another tool, you just need to know how to use it.

    • @anon_y_mousse
      @anon_y_mousse 2 года назад

      @@noxagonal Actually, it's remarkably easy if you don't abstract it. Just think of each class as a struct and ignore the vtable, or don't ignore the vtable and learn how C++ does things internally. Either way, seeing the implementation makes it easier to understand the high level garbage that C++ throws at you.

    • @noxagonal
      @noxagonal 2 года назад

      @@anon_y_mousse Okay... Lets break it down a little. There's no vtable without virtual functions btw.
      Both of these have the exact same memory layout, size and alignment. They're identical in memory.
      C:
      struct X { ... };
      struct Y { ... };
      struct Z { X x; Y y; ... };
      C++:
      struct X { ... };
      struct Y { ... };
      struct Z : X, Y { ... };
      Notice in both cases, Y struct memory is stored after the X struct. You may imagine the Z is a culmination of all "parent" structs plus it's own members. X and Y cannot occupy the same memory within Z.
      Getting hold of the second "parent" is easy, in C you'll just take z.y and in C++ you'll just access the members directly.
      How about the other way around? Could you cast from Y to Z? C++ can, C cannot. Why? It's because C++ tracks this memory offset between Z and it's parents and applies it at compile time when casting. C also could track this offset too, but this mechanic does not have a dedicated interface for it in C.
      The problem I was describing in my original post is, if you cast from Y to Z via void pointer, you circumvent this automatic address adjustment and your memory will point to the wrong place.
      How to fix it... Well in C you can create a dedicated casting function like ( CastYToZ(y_address) ) that take the offset of the "parent as member" and subtracts it from the given address. It's fairly manual though and you can't make multi-level inheritance without chaining these. In C++, you either never cast void pointer to any parent type directly or stick with single inheritance only. Or you go one step forward and abstract by designing a different kind of system.
      In the end, you can do this in C to some extent, but C++ already has functionality designed for this. It's just that you may accidentally circumvent it, just like you may forget to call a function in C and your memory accesses are out of whack.

    • @bluesillybeard
      @bluesillybeard 2 года назад

      @@noxagonal Very well explained. As many have said, C++ is just C with more stuff (which can be good or bad, depending on who you are and what you are doing)

  • @monochromeart7311
    @monochromeart7311 2 года назад

    When you ran *make* at 1:46, it used *clang++* . But in 2:03 you ran *make* again (with no visible changes except for changing which file you're editing), and it used just *clang* .
    Did you change the makefile and cut it out, or is it a weird *make* feature?

    • @JacobSorber
      @JacobSorber  2 года назад +2

      The makefile switches depending on whether the extension is .c or .cpp. I accidentally started editing the .cpp file, and then switched to the .c file.

    • @monochromeart7311
      @monochromeart7311 2 года назад

      @@JacobSorber I'm wondering how does the makefile switch without you telling it explicitly?

    • @wanderingthewastes6159
      @wanderingthewastes6159 2 года назад

      @@monochromeart7311 he does tell it explicitly?

    • @monochromeart7311
      @monochromeart7311 2 года назад

      @@wanderingthewastes6159 he just calls the make program through the terminal, I don't see where does he specify the extension?

    • @JacobSorber
      @JacobSorber  2 года назад +5

      @@monochromeart7311 My makefile has two pattern rules.
      %: %.c
      $(CC) $(CFLAGS) $< -o $@ $(LFLAGS)
      %: %.cpp
      $(CPP) $(CFLAGS) $< -o $@ $(LFLAGS)
      One will generate a binary from a .c file. The other from a .cpp file. Elsewhere in. the makefile it specifies that I want to build "example" and "example2". So, if I modify example.c, then the first rule will be used to compile "example". If I modify "example2.cpp" then it will use the second rule to compile "example2".

  • @AGBuzz182
    @AGBuzz182 2 года назад +2

    Imo, c++ has it backwards. void * is one of the few types that i think you shouldn't have to explicitly cast to and from. I think casting between types of different widths, and between integers and floating point numbers (and of course between pointers to different types of which neither is void) should have to be explicit though.

    • @fullfungo
      @fullfungo 2 года назад +1

      If void* cast was implicit, you could cast
      int* -> void* -> float* implicitly.

    • @AGBuzz182
      @AGBuzz182 2 года назад +1

      @@fullfungo it is implicit in c, and i don't find the situation you describe really happens by accident. Of course i think you should use void * very sparingly in general, but for the situations where it is useful, namely different kinds of generic interfaces, the implicit cast just makes your code that much nicer. This is my opinion

    • @fullfungo
      @fullfungo 2 года назад +1

      @@AGBuzz182 I see 0 use cases for void* in modern C++.
      If you want generics, we have templates.
      If you want unknown data, we have std::any.
      Void* is simply obsolete it modern C++.
      Edit: it is also very unsafe, which is one of the big reasons people avoid it.

    • @AGBuzz182
      @AGBuzz182 2 года назад +1

      @@fullfungo I'm sure that's all true, but plenty of c++ programmers don't use "modern c++". Even beyond that, i was really mostly talking about what i would wish for in an eventual c "replacement". Which i suppose makes it a bit misleading that i mentioned c++ in the first place; my bad.

    • @fullfungo
      @fullfungo 2 года назад

      @@AGBuzz182 I know. There are a lot of reasons people may use “old” C++: old codebase, compatibility, workplace requirements, etc.
      But this means that C++ committee would have to change “old” C++ standards, which they don’t do.
      Also, this would break “old” C++ code just as bad as updating to “modern” versions. So if you want this change, you need to update your C++ version; then why not update to C++20? And while doing this, the need for this feature disappears.

  • @preyumkumar7404
    @preyumkumar7404 Год назад

    is the machine you are using 32bit? why does it give an 8byte address. 64bit machines should have 16bytes of address right ? 😢

  • @Ak4n0
    @Ak4n0 2 года назад

    Básicamente el tipo de los puntero sirve para decirle a C cuántos bytes debe tener en cuenta desde el valor del puntero:
    Si es un:
    char * : 1 byte
    int * : 4 bytes
    double * : 8 bytes
    etc.
    Es decir, sirve para la aritmética de punteros y la notación de array:
    Suponiendo (teórico):
    char *pc = 0;
    pc + 1 apuntará a la dirección 1.
    int *pi = 0
    pi + 1 apuntará a la dirección 4.
    Lo mismo con pc[1] y pi[1].
    void * es el puntero universal. Sin dimensión. Sólo es un puntero. Por eso no puede C derreferenciarlo porque no sabe qué tamaño tiene que darle al dato: si 1, 4, 8 o 1000 bytes.
    En cambio la aritmética de punteros si se lleva porque 0+1 = 1, trata como un entero la dirección del puntero y por tanto:
    void * pv = 0;
    pv + 1 apuntará a la dirección 1.
    De nuevo hay que recordar que, aunque se pueda hacer aritmética de punteros, y notación de array, sobre void * no se puede derreferenciar pues void no tiene información de tamaño al no ser un tipo de variable válido.

    • @user-lv6qm3fj2z
      @user-lv6qm3fj2z 2 года назад +1

      No idea what you said, but good job, man, keep it up :D

    • @zyghom
      @zyghom 2 года назад +1

      zgadzam się całkowicie z kolegą przedmówcą

    • @sanderbos4243
      @sanderbos4243 2 года назад +1

      Translated to English:
      Basically the type of the pointer is used to tell C how many bytes it should take into account from the value of the pointer:
      If it is a:
      char * : 1 byte
      int * : 4 bytes
      double * : 8 bytes
      etc.
      That is, it works for pointer arithmetic and array notation:
      Assuming (theoretical):
      char *pc = 0;
      pc 1 will point to address 1.
      int *pi = 0
      pi 1 will point to address 4.
      Same with pc[1] and pi[1].
      void * is the universal pointer. No dimension. It's just a pointer. That's why C can't dereference it because it doesn't know what size to give the data: 1, 4, 8 or 1000 bytes.
      On the other hand, the arithmetic of pointers, if it is carried out because 0+1 = 1, treats the address of the pointer as an integer and therefore:
      void * pv = 0;
      pv 1 will point to address 1.
      Again, remember that, although pointer arithmetic and array notation can be done, void * cannot be dereferenced because void has no size information as it is not a valid variable type. .

    • @Hauketal
      @Hauketal 2 года назад +1

      The values you give are very specific to some implementations. The only size that is always used (by definition) is 1 for char.

    • @Ak4n0
      @Ak4n0 2 года назад

      @@Hauketal sí, es cierto. Aún así en algún sistema un char podría ser de 2 bytes (no me acuerdo dónde leí esto), pero es cierto que las definiciones de los tipos básicos de C estándar dejan mucho a la imaginación del implementador. De todas formas es para representar la idea y los valores son tomados de un sistema genérico.

  • @maxaafbackname5562
    @maxaafbackname5562 2 года назад

    Nice, but you forget to tell why there is no cast required the other way around.
    For example in the call to free().
    I thinks that makes it more clear why the cast was added to C++.

  • @thisisnotok2100
    @thisisnotok2100 2 года назад

    My guess is void* is how python/php handle dynamic typing

  • @zyghom
    @zyghom 2 года назад

    I was always thinking: how do you know if the piece of code is C or C++? now, from your video I realised: it is only a matter of ... which compiler you use (considering the code is valid for both: C and C++). Did I get it correctly?

    • @damnstupidoldidiot8776
      @damnstupidoldidiot8776 2 года назад

      If it's using C++ exclusive features, definitely C++, otherwise, probably C.

    • @JacobSorber
      @JacobSorber  2 года назад +1

      Sort of. Most C code will compile using either compiler (or with minor tweaks-like the cast issue in the video). But, there are a lot of C++ features as mentioned in the other response that won't work with a C compiler. It's also a good idea to indicate (in the file extension) what language it's supposed to be (.cpp, .hpp, .c, .h), just to help others spend less time guessing.

    • @zyghom
      @zyghom 2 года назад

      @@JacobSorber I am novice to both languages. I am writing some code and without real background I am actually not sure which language I am using - OK, I am not yet on the classes (which I understand are only in C++) but for any other code I write if someone asked me: "C or C++ you used?" I would have no answer.

    • @VTdarkangel
      @VTdarkangel 2 года назад +2

      @@zyghom one of the best ways I can think of is how are you allocating memory on the heap. If your using malloc, especially without a cast, and free, your likely writing in C. If you're using new and delete, you're writing in C++. Another way is you IO. Anything from the iostream.h, like cout and cin, is C++ whereas anything from stdio.h, like printf, is C. All that being said, all the C stuff will likely work in C++ so the C commands can go either way. However, the C++ specific commands will only work in C++, and, if you're writing in C++, you're likely going to be using atleast some C++ specific commands.

    • @zyghom
      @zyghom 2 года назад

      @@VTdarkangel you wrote exactly what I thought: as long as I use the same commands that are in both languages, no way to say much what language it is. But, will compiling with clang or clang++ change anything then? It should not I would say...

  • @leonm8906
    @leonm8906 2 года назад +4

    I think it should be called undefined.
    A void* is a * to Memory of undefined type (alignment, stride and compiletime "usage" implication are unknown(like is it a char or a number or a float)). A function of type void is function that has not defined a return type.
    Meanwhile Void is commonly defined as "completely empty" which kind of works for void, but not for void* since as long as it points to valid memory, it never is "empty".

    • @HobokerDev
      @HobokerDev 2 года назад

      I always thought of void as "no type"

  • @Sakuraigi
    @Sakuraigi 9 месяцев назад

    A pointer is a pointer. It is data type is irrelevant as it is going to be holding a memory address whatever its data type. The data type is only useful if you are planning to do pointer arithmetic. Hope I am right xD

    • @Sakuraigi
      @Sakuraigi 9 месяцев назад

      And also it is important if you plan to dereference it so the compiler can know what kind of value it should expect to avoid type errors ar run time

    • @Sakuraigi
      @Sakuraigi 9 месяцев назад

      It feels kinda useless to use void pointers in c++ if you are going to cast them in the end 😂

  • @hc3d
    @hc3d 2 года назад

    In my opinoin void pointers are annoying to work with, and char pointers make much more sense in all cases. Because evertime you use the subscript operator, with char[N] you can specify an offset of N bytes. but with void[N] you specify an offset of N*4 bytes, which adds extra mental gymnastics to the equation. Char pointers are already used for dealing with binary data, I'm not sure why the C standard lib does not use if for functions like malloc and free. Perhaps I am wrong, but that is how I feel about it.

    • @noxagonal
      @noxagonal 2 года назад +2

      It's mostly about declaring intent to the user. If you see a char pointer, you'd assume it's a string. If it's a void pointer you know it's any data.

  • @elclippo4182
    @elclippo4182 2 года назад

    If void pointers cannot be dereferenced, how does memcpy dereference its arguments then? 🤔

    • @stefan-danielwagner6597
      @stefan-danielwagner6597 2 года назад

      Just a wild guess as I haven't read the memcpy implementation, but all the function cares about is the number of bytes, so it does an internal cast to unsigned char* and byte by byte copy can be performed (obviously, memcpy is written more efficiently than this, but this could be a working implementation).

    • @bluesillybeard
      @bluesillybeard 2 года назад

      Not certain, but I would do it just by casting it to a char* or byte* and copy it one byte at a time.

    • @noxagonal
      @noxagonal 2 года назад

      It casts it some 1 byte type that can be de-referenced, like char. Often the memcpy function results in some very high performance assembly however, so either it has been written in assembly originally or the compiler did it. Regardless the implementation looks a little different from what you might expect.
      If you're curious how to implement memcpy efficiently... You'll want to read and write minimum of 128 bits, up to 512 bits at once (see uint128_t, __m256i __m512i and related load and store operations) so you'll get the benefit of the bandwidth. But you'll have to make sure the memory is properly aligned for these vector load and store operations so you'd copy one byte at a time until you get to a position where memory is properly aligned for vector operations, then do the bulk of the copying that way, then copy the reminder byte by byte again.

  • @georgecop9538
    @georgecop9538 2 года назад

    Want to see if you're running on x64: sizeof(void*) * 8 == 64

  • @handlewithoutsuitcase
    @handlewithoutsuitcase Год назад

    Play-Rewind-Play-Repeat, untill I get it.))

  • @skeleton_craftGaming
    @skeleton_craftGaming Год назад

    I mean, I don't understand why people have such a hard understanding of what void pointers are... If void denotes a function has no type then it only logically concludes that a void pointer it would be a pointer with no underlying object...

  • @Sakuraigi
    @Sakuraigi 9 месяцев назад

    I didn't understand the logic at 7:20 thought

  • @mrJety89
    @mrJety89 2 года назад +1

    Having a void pointer is pointless.

    • @mrJety89
      @mrJety89 2 года назад +2

      No, it's typeless.

  • @kitanowitsch
    @kitanowitsch 2 года назад +1

    0xBABACECA

  • @AaronMatlock
    @AaronMatlock 2 года назад

    You shouldn't malloc like that into an int unless you want random hard faults that can move as you change code, try to add debug code, etc. due to memory alignment of malloc not being guaranteed on all platforms.

    • @xenobino8432
      @xenobino8432 2 года назад

      From malloc(3):
      "The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type."

    • @AaronMatlock
      @AaronMatlock 2 года назад

      What that means is platform specific and at times configurable.

  • @panjak323
    @panjak323 Год назад

    aVoid Void.

  • @Firespectrum122
    @Firespectrum122 Год назад

    "Nietsch-aaaaaaaay"

  • @TranscendentBen
    @TranscendentBen 2 года назад

    I CRINGE every time I see an int assumed to be eight bytes. I started learning C before the ANSI C K&R came out, and an int was two bytes. I still write a lot of code for Arm Cortex M 32bit microcontrollers on which an int is four bytes. Arduino is still popular with twp-byte ints (the original Arduino uses an AVR 8-bit processor, but of course C has a minimum int size of 16 bits).
    Just yet another rant about the vagarities of the C language (and this one even applies to C++ as well).
    Get off my lawn!

  • @Byynx
    @Byynx Год назад

    This video just removed all the void from void pointers.

  • @Jkauppa
    @Jkauppa 2 года назад

    its just a pointer bro

  • @skynowa2626
    @skynowa2626 2 года назад

    Больной человек

  • @ordinarygg
    @ordinarygg 2 года назад

    Learn Rust instead. Thank me later.

    • @xequals-pc1wl
      @xequals-pc1wl 10 месяцев назад

      @ordinarygg That's the language where you have to put "unsafe mutable" on every line if you want to get anything done, right?

  • @sanderbos4243
    @sanderbos4243 2 года назад

    Awesome video!