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".
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.
@@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; }
@@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.
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!
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 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
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++.
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.
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?
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.
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?
@@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!
@@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.
@@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.
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.
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.
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?
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.
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.
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*.
"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.
@@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...
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.
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.
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.
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).
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?
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 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.
@@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
@@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.
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 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.
@@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.
@@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.
@@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)
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?
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 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".
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 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
@@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.
@@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.
@@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.
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.
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 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.
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++.
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?
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.
@@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.
@@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.
@@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...
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".
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
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.
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).
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.
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...
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.
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!
Hearing that this is "a pointer VOID of any type information" was so enlightening.
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!
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".
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.
@@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; }
@@HansBezemer Wow, thanks for sharing!
@@HansBezemer This is very interesting! thanks for sharing
@@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.
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
Usually do not click the like button but this really helped me
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!
@Jacob
Thank!
you!
You're audio is way better today.
Thank you!👍👍👍
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).
auto keyword exists
@@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
The best way to write C++ is to start each file with extern "C" {
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++.
Your content is really good! thank you so much
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.
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?
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.
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?
@@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!
@@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.
@@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.
Is It Better to use template instead of void pointer in c++?
That's really a funny one, with 'feed beef', 'coffee' and 'dead beef'...😂
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.
thank you👍
You're welcome.
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.
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.
Great video!
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?
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.
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.
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*.
"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.
@@richardlyman2961 Not all arrays to be qsort()'d are arrays of ints...
@@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...
Hey, what editor are you using?
That’s visual studio code
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.
Hehe that's exactly what I've done with a linked list library i made
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.
Wait did you just cast a left hand operand? didn't know we can do this!
Thinks like void pointers and function pointers are proof that C is not low-level or mid-level as most people call it
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.
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).
I was clicking all over my screen and here I am
Welcome. I hope you enjoyed where your random clicking landed you.
If you're in C++ use templates instead of void pointers in order to structure your code by using better practical techniques
May I ask how your vscode has the same font as msvs and what font is it!
You can change the vscode font in preferences. Google("What font does MSVS use by default?") --> "Cascadia Code"
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?
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.
@@noxagonal what do you mean by ‘does compile time check - C-style cast does not’???
( i know practically nothing about “correct” C++ )
@@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.
@@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
@@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.
Can you please do a video on char pointer and const char pointer and their weird behaviour when passed in functions etc.
Probably. Can you clarify which "weird" behavior you're most interested in?
Nice vid
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.
Or stick with C and write your "inheritance" code however you want.
@@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.
@@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.
@@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.
@@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)
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?
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.
@@JacobSorber I'm wondering how does the makefile switch without you telling it explicitly?
@@monochromeart7311 he does tell it explicitly?
@@wanderingthewastes6159 he just calls the make program through the terminal, I don't see where does he specify the extension?
@@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".
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.
If void* cast was implicit, you could cast
int* -> void* -> float* implicitly.
@@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
@@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.
@@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.
@@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.
is the machine you are using 32bit? why does it give an 8byte address. 64bit machines should have 16bytes of address right ? 😢
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.
No idea what you said, but good job, man, keep it up :D
zgadzam się całkowicie z kolegą przedmówcą
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. .
The values you give are very specific to some implementations. The only size that is always used (by definition) is 1 for char.
@@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.
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++.
Why?
My guess is void* is how python/php handle dynamic typing
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?
If it's using C++ exclusive features, definitely C++, otherwise, probably C.
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.
@@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.
@@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.
@@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...
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".
I always thought of void as "no type"
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
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
It feels kinda useless to use void pointers in c++ if you are going to cast them in the end 😂
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.
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.
If void pointers cannot be dereferenced, how does memcpy dereference its arguments then? 🤔
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).
Not certain, but I would do it just by casting it to a char* or byte* and copy it one byte at a time.
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.
Want to see if you're running on x64: sizeof(void*) * 8 == 64
Play-Rewind-Play-Repeat, untill I get it.))
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...
I didn't understand the logic at 7:20 thought
Having a void pointer is pointless.
No, it's typeless.
0xBABACECA
0xAB0BA
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.
From malloc(3):
"The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type."
What that means is platform specific and at times configurable.
aVoid Void.
"Nietsch-aaaaaaaay"
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!
This video just removed all the void from void pointers.
its just a pointer bro
Больной человек
Learn Rust instead. Thank me later.
@ordinarygg That's the language where you have to put "unsafe mutable" on every line if you want to get anything done, right?
Awesome video!