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

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

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

  • @SimGunther
    @SimGunther 2 ปีที่แล้ว +12

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

  • @HansBezemer
    @HansBezemer 2 ปีที่แล้ว +42

    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 ปีที่แล้ว +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.

  • @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!

  • @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!

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

  • @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 2 ปีที่แล้ว

      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

  • @cleightthejw2202
    @cleightthejw2202 2 ปีที่แล้ว

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

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

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

  • @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 2 ปีที่แล้ว

      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.

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

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

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

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

  • @Mantorp86
    @Mantorp86 2 ปีที่แล้ว

    Great video!

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

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

  • @mariovrpereira
    @mariovrpereira 2 ปีที่แล้ว

    Your content is really good! thank you so much

  • @knofi7052
    @knofi7052 ปีที่แล้ว

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

  • @khomo12
    @khomo12 ปีที่แล้ว

    Thank you!👍👍👍

  • @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 ปีที่แล้ว +2

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

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

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

  • @KuroiMeansBlack
    @KuroiMeansBlack 5 หลายเดือนก่อน

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

  • @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"

  • @majinuub619
    @majinuub619 2 ปีที่แล้ว

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

  • @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)

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

  • @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 TH-cam ate my reply. XP
      I'll see if I can dig out that link somewhere.

  • @erbenton07
    @erbenton07 ปีที่แล้ว

    Hey, what editor are you using?

    • @d.sherman8563
      @d.sherman8563 ปีที่แล้ว

      That’s visual studio code

  • @marwankhaledkasem9335
    @marwankhaledkasem9335 2 ปีที่แล้ว

    thank you👍

  • @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?

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

  • @kristaqvin
    @kristaqvin 2 ปีที่แล้ว

    Nice vid

  • @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"

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

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

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

  • @thisisnotok2100
    @thisisnotok2100 2 ปีที่แล้ว

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

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

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

  • @Sakuraigi
    @Sakuraigi 10 หลายเดือนก่อน

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

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

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

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

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

  • @Sakuraigi
    @Sakuraigi 10 หลายเดือนก่อน

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

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

  • @mrJety89
    @mrJety89 2 ปีที่แล้ว +1

    Having a void pointer is pointless.

    • @mrJety89
      @mrJety89 2 ปีที่แล้ว +2

      No, it's typeless.

  • @handlewithoutsuitcase
    @handlewithoutsuitcase ปีที่แล้ว

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

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

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

  • @kitanowitsch
    @kitanowitsch 2 ปีที่แล้ว +1

    0xBABACECA

  • @Firespectrum122
    @Firespectrum122 ปีที่แล้ว

    "Nietsch-aaaaaaaay"

  • @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!