@@卛 there was _Bool since C99 and a proper "bool" since C23 (it's undefined how it is implemented, be it a macro or a proper type, but in GCC they are real keywords).
void* is type erasure. The machine doesnt know or care what a "bag of bits" represents, which is why malloc just returns a pointer to the beginning of a (now-valid if non-NULL) memory block, which the programmer then casts to specify how those bits are to be interpreted. Considering again that the machine doesn't care, you can also freely read out the raw bytes of any variable by casting the base address of that variable to a char* pointer and reading out the bytes in a loop as long as you don't read past the sizeof that variable. C is a very raw and low level language that treats the machine as the hardware that it really is. This leaves open enormous room for error but also gives you ultimate power over whatever you want to do.
It doesn't usually treat the machine as hardware though. You're really writing code for the "C abstract machine model". Lotta changes to your code to be found in the process of compiling it for a concrete machine (including but not limited to: undefined behavior). The only thing that really treats the machine as the hardware it really is is assembly/machine code. But even that is just another abstraction layer these days. It gets further translated by microcode into microinstructions.
@@arifroktim3366 yes that is all technically correct, as per the abstract machine model specified by the ISO standard itself, so it is true that I am simplifying further here. Thanks for filling in even more of the picture. Technically also C is still a "high level" language and was especially seen as such in the 70s, although I think most people wouldn't think of it that way nowadays
Structs are a little special here though. Because a fully initialized struct can still have uninitialized padding unless you pre-initialize the memory with something like memset. You won't segfault reading this unintialized padding, but if you make some logical decision based on it, the behavior that results from this is undefined because the data is undefined.
void * works like a generic type in C language. void * can be anything. You can create abstractions and let the user handle his data. curl library works that way.
@@samuraijosh1595 yes it allows you to implement your own polymorphism. The difference with a genuine OO language is that C doesn't know anything about your types in relation to each other. In C++ since it knows the class hierarchy, this can help debugging / catching some errors at compile time
@@samuraijosh1595no it's always a pointer not "anything", it could be NULL, pointer to int, double, structs, functions which follow alignment rules. C is a bit like a portable assembler which you can map to actual hardware types and instructions. So you can implement polymorphism by using structs with a pointer to the data's class with operation functions on your type that could implement inheritance too. You can allocate a block of memory that the operation functions interpret in various ways even say reusing a pointer as an int. Debugging such code can become tricky though as at some point what MUST be a foo was actually a bar due to some rare circumstance. C++ was first implemented in C as a pre-processor not a stand alone compiler.
@@NikolajLepka With great power comes great responsibility
ปีที่แล้ว
@@forgetfulfunctor1 You explain basically what glib does for C. C allows you to implement a language on top of a language. I wonder if Objective-C abuses this ability in C.
In C you are not required to cast void* to other pointer type. The call to malloc without casting the return is normal C code. It won't giver error nor warning. C++ is another story, in that language you indeed need to cast the return of malloc.
Had a homework in my OS class where we used void pointers heavily to implement a fairly simple file system. It was great to lump data of files into a void pointer and put them into blocks within the FS and made data manipulation with memset relatively easy if you knew the offset calculations. Void pointers are an essential for any C programmer and are versatile when data type isn't an issue.
We had programming homework a few weeks ago involving void pointers. They're incredibly versatile and useful if you need an array or matrix that stores different data types in its cells rather than being the same data type on every cell.
@@diobrando7642 While I agree classes are fancy structs, one must know that inheritance and polimorphism are much more difficult to implement than emulating a struct with void pointers and macros actually is. And a struct is as much of a non-pointer as int is. Its size is determined by the data it stores (and the padding, of course). That size determines how much data is copied during an assignment. If a struct takes up 16 bytes, then it will be 16 bytes that are copied when the value of one of its instances is assigned to another instance. May as well say it is an inlined memcpy of 16 bytes from the address of the first instance to the address of the second instance, but we can say the same when assigning a value from an int variable to another int variable.
@@VaheTildian I highly doubt there is a course on this specific argument, but you can learn about them in a C course, I don't recommend using them though, insecure asf.
Thanks. I was watching this for no particular reason and realised that the code I wrote last week didn't cast any void pointers returned by malloc, so I've just checked and I got no compiler warnings. You've just explained why.
@@Brad_Script here are the relevant quotes from K&R 2nd ed: "The main change in ANSI C is to make explicit the rules about how pointers can be manipulated, in effect mandating what good programmers already practice and good compilers already enforce. In addition, the type void * (pointer to void) replaces char * as the proper type for a generic pointer." p93 (ch5) "The valid pointer operations are assignment of pointers of the same type, adding or subtracting a pointer and an integer, subtracting or comparing two pointers to members of the same array, and assigning or comparing to zero. All other pointer arithmetic is illegal. It is not legal to add two pointers, or to multiply or divide or shift or mask them, or to add float or double to them, or even, except for void *, to assign a pointer of one type to a pointer of another type without a cast." p103 (ch5.4) "Unlike the pointer-to-pointer conversions discussed in §A6.6, which generally require an explicit cast, pointers may be assigned to and from pointers of type void *, and may be compared with them." p199 (A6.8)
Ah, I was wondering about that. It's been a long time since I used C, but I never manually casted malloc. Seems bad practice as well, as you duplicate the type.
void* are great if you have a callback function in C. You pass one along with the function pointer as userdata and it comes back to you as a parameter in the callback ready to be casted since you know the shape(type) of the memory!
Its great, but that it uses void 'no data' to represent this is a little confusing. You are literally saying 'here's a map to nowhere'. What you actually mean is 'here is an address i don't know how to dereference or iterate; an address with no map to resolve it'. This is what distinguishes pointers from addresses, they carry actionable type information about memory. So a void pointer isn't actually what it claims, because what it claims is nonsense. Void pointers don't know enough about the data to function as pointers.
This is good advice, but as a caveat, the function definition of your callback MUST also contain void*, with a conversion to the known type done in the function implementation. Don't try and substitute a uint8_t* or whatever_t* in the function definition itself, because even though x86 will let you get away with it, it's UB.
@@joshuaPurushothaman_ It can be, just not in a standard compliant way (well, until c23, which isn't fully implemented anywhere so it might as well be a compiler extension). The way you would do this is by using empty structs. Struct type identity is nominal; its based on the tag you use to identify its declaration, and not its internal structure. In an environment where you can declare empty structs, you get void-like semantics but with a variety of type identities in this space. So you don't mix your void*s in ways that don't make sense. But standard c does not allow you to declare empty structs, until c23, the definition of what a c struct even was included 'non-empty'. Edit: i guess you could also just wrap the void pointer in a struct. So the type is not `void*` its `struct OpaquePointerOfSomeExternalType { void* vp; }`; That way you get the identify of the struct to distinguish different ways to interpret the void*. I think people shy away from this because it obscures the fact that its stateful memory-related data that can be invalidated externally.
5:45 In C++, this is correct. In C, I can’t get any of gcc, clang, or msvc to warn about that at *all.* People who code in C more than I do say casting the result of `malloc` is at best bad style and should probably be considered a code smell or even a bug, because C casts are powerful enough you don’t want to use them when you don’t have to, and void* can be implicitly converted to any pointer type. I don’t like that at all, but I think it is true for C, which is part of why I stick to C++ when possible.
It's also very useful when you're dealing with callbacks. You can pass a void pointer (pointer to some arbitrary application data) that for example an external event library will hold on to and then pass as argument back to you when it's calling you back in the function you specified. Typically something like register_callback(callback_function, (void*)arbitrary_data); and at some later time the library calls callback_function(arbitrary_data); The library can't know about your type so void * is useful here.
Putting it another way, the library that will be calling your function doesn't know or care about the type of its argument, but you made the function so you know what type the argument will be (a pointer to another class you made). This is a great example, since many compilers will warn about it. It is a bad practice, but if you want complete type safety you'd use C++. Many libraries just use C, which only supports basic "types". A beginner would only need to learn what these terms mean, then they'd be well on their way to expert programmer status.
I just took a job working on firmware and had no experience with it, whatsoever. This channel has been instrumental in helping me to understand how everything works. Thank you!
@@kro3q It started off as a co-op. I was in college for mechanical engineering. I took some low level programming classes while I was there and showed them I could write C code. That was really it
Great content you've been putting out lately! I really appreciate you going over the core tricks and workings of C/ASM, especially since I just got my first job as a embedded dev. Helps out a ton, keep up the amazing work 🙏🙏
It can also be passed as args. If you have 1 arg and its size is 16 bytes, it can be passed as if you had 4 ints. The stack is used only once the first 4 args "registers" are taken (mostly).
No yeah that's what I thought. "bad practice" is different from "can't" (said literally every C programmer ever, lol). Like a struct Vector2 - you might have an in-place modification with pointers, and a "return a modified copy" version with just pass-by-value. Having played more with Rust, staying immutable where possible is cool with me. Only reason I'd think it's bad practice to *not* modify by reference is that the compiler probably won't optimize out the fact that your code *could* have been written as an in-place/by-reference modification - leaving you with inefficient resource usage... what do you think? @LowLevelLearning
I would argue there's an obvious advantage to using the stack in that you're simply not dealing with pointers. It's definitely not always possible, but when it is, it's one less spot for making a memory allocation mistake. I also wonder if compilers do/could optimize that type of function call... given it performs the same results on the same data? typedef struct Vec2 { double x, y; }; Vec2 norm(Vec2 a) { double length = //... return Vec2(new_x, new_y); } void norm_in_place(Vec2* a) { a->x = //... a->y = //... }
I think a better example about using pointers is the need for adjusting the actual value on another variable you may have, depending on a condition. Or likewise, just having an indexable array. Also justifies using malloc for your array, which is one of the more common approaches.
type erasure is useful if you have to hand build dynamic dispatch/ polymorphism. Ideally you would avoid this as much as possible, but it's sometimes the best solution.
5:50 - this is incorrect. If you're writing C, you should not cast return value of malloc. Conversion to and from void pointer are implicit and in the case of malloc doing the cast may silence an issue of stdlib.h not being included (subject to C version). And if you're writing C++ you should use new. PS. A good idiom to use with malloc is to use sizeof with an expression rather than the type as in: "Person *person = malloc(sizeof *person);" This way, if you ever change the type of the variable, there's no risk that you forget to change the allocation size.
depends on the compiler... with msvc without a cast I get "a value of type void* cannot be used to initialize an entity of type int *", so an explicit cast is required.
Use case: you want to implement some specific memory management system, i.e. some kind of heap. Maybe for a compiler, language, or OS of your own. (In my case, in my OS class we did have to implement a "free list" using structs, void pointers, and some syscall stuff for an assignment. Turns out there are a lot of ways a heap can work.)
Also great if you want to implement generic functions such as sorting functions which can sort an array of any type, given a pointer to a comparison function for that type.
void* is also useful if you want to encapsulate away the implementation details of a data type away from client code using it - typedef the "handle" type to void* in the header and define the actual type internally in the library. Useful for avoiding ABI breakage across library versions.
You also want to use this type when calling a general purpose function with a callback and void* userData parameter. For example, if you use some kind of network callbacks, you want to access some shared state and mutate it some way. As long as you know what type was passed under void*, you can safely reinterpret cast it into target type and use this piece of memory
For me an easier way was to look at it like this: a pointer never has a type. A pointer is a memory address, nothing more. No matter if it is int* or void*, it is a memory address. Period. The only thing you are doing by putting a type in front is sort of “hinting” to the compiler what the size is of the data that lives there, so when you do dereference it, the compiler knows how many bytes to get. It is more accurate as well than saying you have an int pointer. A pointer is a memory address, not a type.
That's how I see it as well since the CPU doesn't have any way of knowing what is there. For example, when you write char* foo, the compiler is essentially making a promise to you that it will only ever load from that address one byte at a time. Same with "unsigned int x = 3;", the compiler promises that it will only use unsigned operators on this variable. The CPU doesn't know or care what you or the compiler intended, it's not that smart
“What is a pointer?” - A thing that tells you where something else is. Int pointer tells you where an int is. “Why use pointers?” - Because sometimes you want to talk about where to find something rather than the thing itself. FYI: - passing structs/unions to functions by value is acceptable, a pointer is not needed. It makes a copy when you do this, of course. - malloc does not require a cast. Void pointer never needs a cast. - void pointer should be used when the underlying type is unknown at compile time.
A project I work with regularly seems to think that void pointers exist as a means of documenting and explaining the code. When you use them everywhere, anybody that uses your code needs to figure it out really well to use it, so it becomes self-documenting once they spend the time analyzing it, so you can save time writing documentation.
In general, void pointers are used when you have to work with memory addresses and deliberately say "I do not care at all what the memory here means". For example, malloc() returns a void pointer, because the function can not know what the memory you allocated means.
i once found a great usecase for void ptr when i wrote an event system, cause every participant is able to send a notification to the other members with the event id and an additional optional const void ptr. that ptr is there to give the participants additional information about that event and it could be anything so it's void
Are you sure that at 5:45 the compiler will throw a warning? If am not wrong it will throw an error, but only with C++, it is totally fine to implicit covert a 'void *' to another kind of pointer in C.
Just a little remind: what you said at 2:13 isn't totally true. You can pass a strct type variable to a function by value. This is an example. #include struct Person { int id; char name[64]; }; void print(struct Person p) { printf("p.id=%d p.name=%s ", p.id, p.name); } int main() { struct Person pp = {1, "John"}; print(pp); return 0; } Anyway, still learn a lot from this video. Thank you.
Passing structures by value is supported in C89 and later. Generally not a great idea, but it is supported. One of the most compelling reasons is to allow side effects on the object used as an argument; that is, let the function directly modify the callers object. Pointers to void allow type agnostic treatment of objects. They must be cast to a pointer to a known type before being used. I have written a number of libraries that allocate an object for context information specific to the thead using the library. Rather than exposing the structure to the client code, a handle to the context is returned by the init function, and the handle is of a typedef of void*, making it opaque to the caller. In that context, 'void*' is being used for data hiding. There are many other good uses of void pointers. I learned C long before the standards committee was formed, had to deal with the implementation differences between the K&R compiler, PCC, and various vendor implementations. I even worked for one of those vendors (MWC). The standardization of the core language is what ensured its long reign as the systems programming language of record.
a pointer does NOT always contain the address of another variable. It can point to raw data or a function or unintialized memory or even nothing at all.
at the 5:56 mark, you state that you need to cast the pointer type. It looks like you're using standard C, so that's not actually true. In C the void * type is compatible with all pointer types, so no casting is required. If, however, you're using a C++ compiler, because of the added type safety of the C++ language, you'll need to use a cast to your resulting pointer type, but if you're using C++ then you should probably be using new, and delete, and templates, to avoid the use of void *.
The logical question is actually "Why do non-void-pointer data types exist?" At runtime all software is basically just a list of void pointers to data and instructions in memory. The answer is of course convenience for our human brains, but those abstracted high-level types don't easily handle every corner case so the rawest of raw pointers is retained (as is the ability to include a block of assembly.).
The answer is for compilation really. I'd argue runtime typing is also just bits doing computation where the logic of the program makes sense of itself during runtime. That's just an extension of what's happening at compilation.
@@williamdrum9899 Those exist at the machine code level. A register is just a group of anonymous bits, the way an operation is performed on those bits is what gives it signed or unsigned properties.
Note that even in C++, where you can use the new-operator, there's still a place for void: accessing raw memory, like you often do in embedded or low-level programming. And that's pretty much the only real use in C++ for void.
I learned my pointers on old PLCs. Void pointers are the only thing they have, you have to cast that pointer into a type every time you dereference it. I was really confused when I learned about typed pointers in C. „How does an address have a type???“ :)
In a program I wrote, I needed a linked list of variables, specifically, a linked list of many different structs, these structs had some things in common for reading purposes. Without void*, i wouldn't have been able to make that, since the type of variable isn't known beforehand.
Finally someone who writes if (NULL == some_ptr) instead of the more usual other way around with NULL being on the right. I always do it like this, but my colleagues always complain about it, no matter how much I try explaining to them that if you write it like this, it won't compile if you accidentally only use a single = instead of the double ==. Unlike "if (some_ptr = NULL)", which will compile no problem but will not do what you intended... Of course, a simple "if (some_ptr)" would do the trick, but I rather be explicit (one colleague is known for writing it like this and then putting a "// make sure that it's not NULL" behind it -duh), especially since it shouldn't make any difference in the compiler output.
@@williamdrum9899 there are a few things that C lets you do, that fall in this category. You can do them, your source code looks shorter, it introduces problems and still your binary isn't any smaller. But that's never going to change. I also never use anything thdt isn't either a boolean variable or expression in an if statement. So if I check that a pointer is not NULL, I explicity write it instead of if(some_ptr). It will generate the same machine code anyway.
i have used c since 1988. its still thenbest language out there for anything that touches hardware. and i jave done tons of embedded stuff. sure assembly is slightly better when dealibg with the lowest levels but c is the way to go for the majority of the work.
Void * can also be used as a handle to prevent the user of a module to have direct access to the object it represents. The type of the data structure is not exported in the header file, but only know in the source file or precompiled object file or library.
2:11 you still can pass person p by value (and return it...), we use pointers for changing data "in place" so passing pointer you change data that pointer points to instead of making copy of a struct inside function
I found it odd that he said that. I also noticed the sneaky removal of memset which would have shown a compile error. He could have done memset(pPerson->name, ...) or memset(&pPerson->name[0], ...)
Another good place to use void pointers is in functions that dont care what kind of type you are sending to it. lets say you have a function that is void send_data_UART(void * data, size_t data_size) In this function if the data type is in a byte array (uint8_t *) or another struct, the callee would need to dereference their pointer manually, if its not already a byte array. It makes the callee code hella messy. But the send_data_UART does not care what date you give it - it wants a pointer and it will send all the bytes after that pointer.
I am working on a project where someone back in the day decided to have exactly those UART routines take a uint8_t* for the data instead of a void*. But of course what you actually put in there are some structures that are relevant for the protocol that you are using. Way too much casts there that could have been avoided if they had used void*.
@@Nik930714 I know a lot of folks rave about the code generation in STM Cube. While it is nice to get you started, I think the quality of that code is atrocious. If I ever were to work on a STM platform, I would likely have something generated by Cube and then re-do it from scratch, not just ditching their code templates but also Eclipse as an IDE. It still can't Ctrl-Tab between editor tabs in 2023. Please leave my computer. LOL
I first ran into these when learning to use FreeRTOS, to pass arguments to tasks. It was weird the first time I read about it but it made sense to realise it was basically used to make the type for the function like an "any type". You'd make the memory. Cast it to a void* to pass it to the task, and then cast it back to the original type in the task
At 2:13 you claim, that you could not pass the structure Person by value ... but this is actually wrong. While C-language does not pass arrays by-value, it does indeed pass structures by value if you use a struct variable rather than a struct * variable (as argument to a function). This of course will make the code rather slow (and might have the side effect, that the called routine is only making changes in the copy). However, my point is, C does indeed allow to pass structures by value.
Weird. I only program in assembly for retro hardware and I've never felt the need to pass an entire struct by value (or, for that matter, anything bigger than 2 registers)
Useful for implementing callbacks, when you want separate compilation. qsort is an example. Though std::sort is faster for any particular type, it has to be instantiated for each type it is used for leading to more generated code. With void*, we can avoid that.
You do not have to cast the return of malloc. It is better no to cast it because a void * is designed to be cast to anything. You may hide type problems by doing so. I would welcome if you would correct that statement in the video.
I consider void * to be one of the useful features of C and C++, it allows you convert between variable types like char and int and allows you to hide information from user of your software. Also the fact that you can write a single function to de operations on different types makes it an indispensable type
When I was first learning C++ I was confused by * because it is both an operator and a type specifier. I find code more readable when the * is attached to the type when used as a type specifier, e.g. char* vs to the variable/value when used for referencing it, e.g. *Value, unfortunately the common notation is to always attach it to the value for whatever reason.
@@EdKolis You are right it does, while const and static both apply to both variables, which bring up the question of why would they do something like this?
Yeah, I for some reason feel like ''char* variable'' makes more sense than having the * at the variable itself, but the language certainly has an expectation that it's meant to be the latter of the two. Declaring multiple pointers requires ''char *var1, *var2'' for example, and declaring a fixed-sized c-array as a parameter is ''char (*variable)[T]''. The parser doesn't care in most situations, but it is a thing lol.
Came here to say this, the type is "char*" and should be written as a single word, attaching the asterisk to the variable's name feels weird. ...And back up the ram bus (vroom vroom), going "int* a, b;" _doesn't_ have both variables be of type int-pointer??? Wat.
pnt is when you call a subroutine that in assembly has an offset + base address like $06[R0], the pnt is the base address in R0. C makes sure you understand what your'e doing so it only allows similar pointers, the type has nothing to with the size of a pointer as that is always the system size (like 32bit or 16bit), but as it's just a raw 32/16bit value, passing along anything could work in theory, and you use void to override this check.
Void* can be used to make the type opaque. They'll just recreate a new variable at the start of the function. Like windows window handle and other stuff in C. Real var = (cast) void_argument ---- Also, it's for generic types. You still need to cast it. But it's a way to say: This can have multiple types. Like for malloc. It returns an address. Just that. Hence, it has no type.
You don't need to cast the return value of malloc and it's considered a not-so-good practice to do so in C. In simple words, a void * pointer is a pointer that can point to ANYTHING.
Generally said the you use the void* type everywhere where you would use generics in higher level languages. But you leave the user to cast those void* to the right types, which is what the compile would do when using generics.
There is a very good reason to use an int* , e.g., if you want to return more than one integer from a function call. You can return one as the return value and store the value of the other one in the supplied int*. It's a fairly common idiom, I believe. I can't find an exact example right now but strtol() from cstdlib uses a char** for this exact purpose.
"A void* represents a pointer to anything" is all I think about when I see it. Knowing that, I can typecast (as in "(T*)p") and all is well i n the world, imho.
Hi there, first of all sorry for my english, second of all i love this man's channel, third, i have a doubt and i'm a begginer in C, please don't be rude, here it goes: in 2:06 he said that you could not pass a struct by value to a function because it is greater than the architecture word size, but you actually can, the code compiles and works. So it is a thing about performance? i mean i know a pointer size is less than a large struct size so i it makes sense to pass only the reference, but when you pass a variable smaller than the word size it doesnt matter passing it by value because a pointer max size is the same as the word size? for me it makes sense because i did read somewhere that the word size of an architecture is the same as the directions bus, so the word size full of 1 is the same as the max memory register address, but i don't really know if i am wrong. Am i understanding this well? or am i completely wrong? thanks in advance!
void pointers are pure addresses which make no presumption about the data type. It allows you to pass the address without the type. This makes them dangerous for many things. Some APIs require them and some memory operations use them. They are generally to be avoided when possible. There are other design patterns which can be used in their place most of the time. (Queue to a structure in a thread pool.) There are a few exceptions. These generally involve cases where you only know the address and will never care about the type. A cache line request for example can be void pointer. A stream parser can be a void pointer, until you discover the type from the stream. So void pointer allows you to communicate things.
I really like void pointers. It's fun to play with them. Tho they are pretty easy to fuck up with, still they are fun. They basically allow you to make anything possible even in runtime.
well a pointer to an integer is still useful because your program can keep track if the value changes, even if there's no memory savings versus passing it by value
I had to work on a windows driver once where the top level called lower level routines all with void pointer parameters. The original programmer believed that since the lower level didn't specify any types for its parameters, the driver was more portable. Ugghhh.
Simple analogy for the TS homies here: void* = any/unknown from TS unknown even has the same semantics - you can pass it to any other type without explicit typecast but you have to make a typecast when you need to work with it.
That was an incredibly informative video! Thank you so much! I'm a little lost about the working of the free() function. Could you, or perhaps someone else, shed some light on how free() knows how many bytes it needs to free? It takes in a void pointer and still somehow figures out the length of the memory it needs to release 🤔
The OS usually keeps track of the memory block you request with malloc. If you tell the OS that you no longer need the block it gave you, starting at that address, it will go into its own allocation table and clear the address and number of allocated bytes out of that table. (It doesn't actually delete any of the data that was in that block of memory, the block of memory is just now free to be reallocated by another malloc call. So if you didn't set that data starting at that address to 0, then whoever gets it allocated next can read what you had saved there before)
I think void *ptr; makes the most sense when you think about weird architectures. Okay you can say that char will be the smallest addressable on that machine that maybe does not use bytes but lets say 10 bit somethings - but maybe the machine has opcodes to access 16 bit data when they are from a different memory region and step by 16 bits properly when indexing. In that scenario void is much better solution to hide the type of the thing (like a handle that is a pointer to this or that region!) because properly writing it out is impossible - yet every use of it you should cast it properly to proper pointer type and in theory you can make the compiler seamlessly handle all this. That being said this is very rare - but for example there are indeed microcontrollers where flash can be written-to but with 12 bit values and they also have regular (but smaller) nvram to write to with 8 bit slots... So even though the usual solution is to just make some kind of API that expects you to write 16 bit values with the topmost 4 just ignored, the language itself is actually made to handle also this weird shit situation "well" - by that I mean "somewhat logical"...
I can't think of many instances where I've legitimately used void pointers in my code. Even if I don't know what a blob of memory contains, I usually specify that the smallest unit of data is an unit8_t (because I need at least some sort of reference to work from). A void pointer to me basically means that either the sender or the receiver doesn't know anything about the data, not even what the size of the smallest (or largest) discrete value represented within is. Not even if there is any data there at all. It's like a random GPS coordinate: What's there? I don't know, it could be a business, a house, a tree, a single molecule of water, or nothing at all. What should I look for? I don't know. What should I do what I get there? I don't know. Should I go there at all? I don't know. You should know, it's not my responsibility to know. It's a void pointer.
We should all take a minute to send hateful thoughts towards the person that made the C++ syntax. I was a C-programmer but stopped programming C when C++ came. I mean, come on??? "cout
Love your stuff! Just want to point out that IMO the first half of the video explaining pass by value may be a little confusing to people who are unfamiliar. Your explanation sounds like youre saying “if a value being passed to a function is within a certain byte size it will pass in the ACTUAL object” which is not true. C always passes everything as a copy. Its just that it is feasible and efficient to pass values as copies into functions when they are simple small data types, whereas with bigger data structures C doesn’t allow it. To me, the best way to understand pointers is simply “C passes by value copying, what do you do if you want the actual thing instead of a copy? Pointer!”
They exist because types are a fiction we made to help us do our work. A lack of types is the default state, and C comes from a time before everyone demanded to live in walled gardens. That's not to say C doesn't have bullshit problems that compiler vendors exacerbate by being rules lawyering dicks, but that's a conversation for another time.
"Pointer" was a hardware concept historically. It was an "address" pointer. No concept of a type was associated with the pointer. Any type information was part of the thing located at the address pointed to. The concept of associating a type with a pointer is a mid or high level concept, not a low level concept.
void* malloc is one of the better examples of why generic/templated functions are so useful in a modern language. Something like (MyType*)malloc(sizeof(MyType)) is so much less useful than malloc() where the assignment hints to the compiler the sizeof and cast arguments.
@@ex-xg5hh I believe it is still generics, because you'd have a single malloc function that must operate over arbitrary types. I guess in C-like languages you could use function overloading and type inference to select the appropriate overload, but that seems like the lesser of the two choices.
Can be used in lib's that are dependent one of another, you can not include the header file of one lib in to another lib and vice versa at the same time, so you don't include the header file in the header file but in source file and pass void pointers and cast them, that way you avoid recursive inclusions.
for that use case, ifdef guards and pragma once exit And try to always avoid including headers in headers. That can cause confusion and include unnecessary code. ( unless it's a file without any actual code ) An ifdef guard is when you surround your headers with # ifndef HEADER_NAME_H # define HEADER_NAME_H /* header content */ # endif this prevents the header from being included twice, since the first include defines the macro, and in subsequent included versions the preprocessor will skip over the contents. # pragma once at the top of the header also prevents it from being included multiple times, but does so much more cleanly.
Fun fact: When my dad was a kid, there were no void pointers in "K&R" C, they used to use char pointers. Void pointers were introduced in ANSI C
@@tripplefives1402 there still is no bool
@@tripplefives1402C never had bool. until C23 I think they added a bool keyword
@@卛 there was _Bool since C99 and a proper "bool" since C23 (it's undefined how it is implemented, be it a macro or a proper type, but in GCC they are real keywords).
#define bool _Bool
#define true 1
#define false 0
doesn't count as a boolean to me.
@@卛 what do you think a boolean is?
void* is type erasure. The machine doesnt know or care what a "bag of bits" represents, which is why malloc just returns a pointer to the beginning of a (now-valid if non-NULL) memory block, which the programmer then casts to specify how those bits are to be interpreted.
Considering again that the machine doesn't care, you can also freely read out the raw bytes of any variable by casting the base address of that variable to a char* pointer and reading out the bytes in a loop as long as you don't read past the sizeof that variable.
C is a very raw and low level language that treats the machine as the hardware that it really is. This leaves open enormous room for error but also gives you ultimate power over whatever you want to do.
@@tripplefives1402 right, a pointer just contains a memory address and that's it from a hardware point of view
It doesn't usually treat the machine as hardware though. You're really writing code for the "C abstract machine model". Lotta changes to your code to be found in the process of compiling it for a concrete machine (including but not limited to: undefined behavior).
The only thing that really treats the machine as the hardware it really is is assembly/machine code. But even that is just another abstraction layer these days. It gets further translated by microcode into microinstructions.
@@tripplefives1402 Not exactly, pointer arithmetic depends on the type
@@arifroktim3366 yes that is all technically correct, as per the abstract machine model specified by the ISO standard itself, so it is true that I am simplifying further here. Thanks for filling in even more of the picture. Technically also C is still a "high level" language and was especially seen as such in the 70s, although I think most people wouldn't think of it that way nowadays
Structs are a little special here though. Because a fully initialized struct can still have uninitialized padding unless you pre-initialize the memory with something like memset. You won't segfault reading this unintialized padding, but if you make some logical decision based on it, the behavior that results from this is undefined because the data is undefined.
Void pointers are funded by big undefined behavior to sell more undefined behavior.
@@tripplefives1402 no, because void doesn't have a size.
@@nordgaren2358It does have size lol, for example all pointers have 8 bytes on Ubuntu
Alternatively: void pointers were made by C programmers because they wanted job security, and as long as the memory's leaking, they're getting paid 😂
@@joshuaPurushothaman_ sounds like you've only used high level languages lol
@@hawks3109 true - all jokes though ;) (I use Rust)
void * works like a generic type in C language. void * can be anything. You can create abstractions and let the user handle his data. curl library works that way.
so is it basically polymorphic type like there is in haskell?
@@samuraijosh1595 yes it allows you to implement your own polymorphism. The difference with a genuine OO language is that C doesn't know anything about your types in relation to each other. In C++ since it knows the class hierarchy, this can help debugging / catching some errors at compile time
@@samuraijosh1595no it's always a pointer not "anything", it could be NULL, pointer to int, double, structs, functions which follow alignment rules.
C is a bit like a portable assembler which you can map to actual hardware types and instructions.
So you can implement polymorphism by using structs with a pointer to the data's class with operation functions on your type that could implement inheritance too. You can allocate a block of memory that the operation functions interpret in various ways even say reusing a pointer as an int.
Debugging such code can become tricky though as at some point what MUST be a foo was actually a bar due to some rare circumstance.
C++ was first implemented in C as a pre-processor not a stand alone compiler.
@@NikolajLepka With great power comes great responsibility
@@forgetfulfunctor1 You explain basically what glib does for C. C allows you to implement a language on top of a language. I wonder if Objective-C abuses this ability in C.
In C you are not required to cast void* to other pointer type. The call to malloc without casting the return is normal C code. It won't giver error nor warning. C++ is another story, in that language you indeed need to cast the return of malloc.
The problem was the print statement, not the malloc call.
Had a homework in my OS class where we used void pointers heavily to implement a fairly simple file system. It was great to lump data of files into a void pointer and put them into blocks within the FS and made data manipulation with memset relatively easy if you knew the offset calculations. Void pointers are an essential for any C programmer and are versatile when data type isn't an issue.
We had programming homework a few weeks ago involving void pointers. They're incredibly versatile and useful if you need an array or matrix that stores different data types in its cells rather than being the same data type on every cell.
Void pointers really help understand that each object from C++ is just a pointer the compiler knows how to interpret.
@@Hardcore_Remixer structs are fancy pointers, classes are fancy structs.
@@diobrando7642 While I agree classes are fancy structs, one must know that inheritance and polimorphism are much more difficult to implement than emulating a struct with void pointers and macros actually is.
And a struct is as much of a non-pointer as int is. Its size is determined by the data it stores (and the padding, of course). That size determines how much data is copied during an assignment. If a struct takes up 16 bytes, then it will be 16 bytes that are copied when the value of one of its instances is assigned to another instance. May as well say it is an inlined memcpy of 16 bytes from the address of the first instance to the address of the second instance, but we can say the same when assigning a value from an int variable to another int variable.
Could I have access to this homework and maybe a course about those void pointers? (if there is such a course)
@@VaheTildian I highly doubt there is a course on this specific argument, but you can learn about them in a C course, I don't recommend using them though, insecure asf.
The malloc line only generates a warning if the .c file is compiled as C++. It's perfectly kosher to not cast in C - compile as C and you'll see...
Thanks. I was watching this for no particular reason and realised that the code I wrote last week didn't cast any void pointers returned by malloc, so I've just checked and I got no compiler warnings. You've just explained why.
@@stephenbranley91this is only true for void, if you try to put an int* into a double* the compiler will give you a warning
@@Brad_Script here are the relevant quotes from K&R 2nd ed:
"The main change in ANSI C is to make explicit the rules about how pointers can be manipulated, in effect mandating what good programmers already practice and good compilers already enforce. In addition, the type void * (pointer to void) replaces char * as the proper type for a generic pointer."
p93 (ch5)
"The valid pointer operations are assignment of pointers of the same type, adding or subtracting a pointer and an integer, subtracting or comparing two pointers to members of the same array, and assigning or comparing to zero. All other pointer arithmetic is illegal. It is not legal to add two pointers, or to multiply or divide or shift or mask them, or to add float or double to them, or even, except for void *, to assign a pointer of one type to a pointer of another type without a cast."
p103 (ch5.4)
"Unlike the pointer-to-pointer conversions discussed in §A6.6, which generally require an explicit cast, pointers may be assigned to and from pointers of type void *, and may be compared with them."
p199 (A6.8)
Ah, I was wondering about that. It's been a long time since I used C, but I never manually casted malloc. Seems bad practice as well, as you duplicate the type.
void* are great if you have a callback function in C. You pass one along with the function pointer as userdata and it comes back to you as a parameter in the callback ready to be casted since you know the shape(type) of the memory!
Super neat tip!
Its great, but that it uses void 'no data' to represent this is a little confusing. You are literally saying 'here's a map to nowhere'. What you actually mean is 'here is an address i don't know how to dereference or iterate; an address with no map to resolve it'. This is what distinguishes pointers from addresses, they carry actionable type information about memory.
So a void pointer isn't actually what it claims, because what it claims is nonsense. Void pointers don't know enough about the data to function as pointers.
I'm confused -- can't that be done with more strongly typed function pointer params?
This is good advice, but as a caveat, the function definition of your callback MUST also contain void*, with a conversion to the known type done in the function implementation. Don't try and substitute a uint8_t* or whatever_t* in the function definition itself, because even though x86 will let you get away with it, it's UB.
@@joshuaPurushothaman_ It can be, just not in a standard compliant way (well, until c23, which isn't fully implemented anywhere so it might as well be a compiler extension). The way you would do this is by using empty structs. Struct type identity is nominal; its based on the tag you use to identify its declaration, and not its internal structure. In an environment where you can declare empty structs, you get void-like semantics but with a variety of type identities in this space. So you don't mix your void*s in ways that don't make sense. But standard c does not allow you to declare empty structs, until c23, the definition of what a c struct even was included 'non-empty'.
Edit: i guess you could also just wrap the void pointer in a struct. So the type is not `void*` its `struct OpaquePointerOfSomeExternalType { void* vp; }`; That way you get the identify of the struct to distinguish different ways to interpret the void*. I think people shy away from this because it obscures the fact that its stateful memory-related data that can be invalidated externally.
5:45 In C++, this is correct. In C, I can’t get any of gcc, clang, or msvc to warn about that at *all.* People who code in C more than I do say casting the result of `malloc` is at best bad style and should probably be considered a code smell or even a bug, because C casts are powerful enough you don’t want to use them when you don’t have to, and void* can be implicitly converted to any pointer type.
I don’t like that at all, but I think it is true for C, which is part of why I stick to C++ when possible.
It's also very useful when you're dealing with callbacks. You can pass a void pointer (pointer to some arbitrary application data) that for example an external event library will hold on to and then pass as argument back to you when it's calling you back in the function you specified. Typically something like register_callback(callback_function, (void*)arbitrary_data); and at some later time the library calls callback_function(arbitrary_data); The library can't know about your type so void * is useful here.
Putting it another way, the library that will be calling your function doesn't know or care about the type of its argument, but you made the function so you know what type the argument will be (a pointer to another class you made). This is a great example, since many compilers will warn about it. It is a bad practice, but if you want complete type safety you'd use C++. Many libraries just use C, which only supports basic "types". A beginner would only need to learn what these terms mean, then they'd be well on their way to expert programmer status.
I just took a job working on firmware and had no experience with it, whatsoever. This channel has been instrumental in helping me to understand how everything works. Thank you!
how do you just take a job without related experience D:
seriously as an intern i rarely get a reply from recruiters
@@kro3q It started off as a co-op. I was in college for mechanical engineering. I took some low level programming classes while I was there and showed them I could write C code. That was really it
Great content you've been putting out lately! I really appreciate you going over the core tricks and workings of C/ASM, especially since I just got my first job as a embedded dev. Helps out a ton, keep up the amazing work 🙏🙏
How's the embedded dev job going ? I'm thinking of specializing in low level programming and I wonder how great the jobs are
But you can totally pass something bigger than the word size by value, but it is probably passed via the stack instead of registers.
yeah but I think thats bad practice honestly.
It can also be passed as args. If you have 1 arg and its size is 16 bytes, it can be passed as if you had 4 ints. The stack is used only once the first 4 args "registers" are taken (mostly).
No yeah that's what I thought. "bad practice" is different from "can't" (said literally every C programmer ever, lol).
Like a struct Vector2 - you might have an in-place modification with pointers, and a "return a modified copy" version with just pass-by-value.
Having played more with Rust, staying immutable where possible is cool with me. Only reason I'd think it's bad practice to *not* modify by reference is that the compiler probably won't optimize out the fact that your code *could* have been written as an in-place/by-reference modification - leaving you with inefficient resource usage... what do you think? @LowLevelLearning
I would argue there's an obvious advantage to using the stack in that you're simply not dealing with pointers. It's definitely not always possible, but when it is, it's one less spot for making a memory allocation mistake.
I also wonder if compilers do/could optimize that type of function call... given it performs the same results on the same data?
typedef struct Vec2 {
double x, y;
};
Vec2 norm(Vec2 a) {
double length = //...
return Vec2(new_x, new_y);
}
void norm_in_place(Vec2* a) {
a->x = //...
a->y = //...
}
@@joshuaPurushothaman_ if it makes sense to optimized, it's more than likely optimized already
I think a better example about using pointers is the need for adjusting the actual value on another variable you may have, depending on a condition. Or likewise, just having an indexable array. Also justifies using malloc for your array, which is one of the more common approaches.
You‘re one of my favorite TH-cam channels at the moment. I love the content and the comment section is pretty interesting too.
type erasure is useful if you have to hand build dynamic dispatch/ polymorphism. Ideally you would avoid this as much as possible, but it's sometimes the best solution.
5:50 - this is incorrect. If you're writing C, you should not cast return value of malloc. Conversion to and from void pointer are implicit and in the case of malloc doing the cast may silence an issue of stdlib.h not being included (subject to C version).
And if you're writing C++ you should use new.
PS. A good idiom to use with malloc is to use sizeof with an expression rather than the type as in: "Person *person = malloc(sizeof *person);" This way, if you ever change the type of the variable, there's no risk that you forget to change the allocation size.
depends on the compiler... with msvc without a cast I get "a value of type void* cannot be used to initialize an entity of type int *", so an explicit cast is required.
@@marsovac, if you’re using C compiler, casts between void* and some_type* are implicit. You’re probably compiling the code as C++ project.
If you change the variable type, it would throw a warning, casting becomes more readable :)
@@RamsLiff, that’s why you do ‘person = malloc(sizeof *person)’ and then the code works just fine after you change type of ‘person’ variable.
Honestly, I think I've never seen such an amazing explanation. You're way of explaining and showing stuff is unbelivably great, thx m8
Do not cast mallocs. In C, void* can be implicitly converted to and from any pointer type. It does not give errors, this is by design.
when you use c++ like c
Use case: you want to implement some specific memory management system, i.e. some kind of heap. Maybe for a compiler, language, or OS of your own.
(In my case, in my OS class we did have to implement a "free list" using structs, void pointers, and some syscall stuff for an assignment. Turns out there are a lot of ways a heap can work.)
Also great if you want to implement generic functions such as sorting functions which can sort an array of any type, given a pointer to a comparison function for that type.
void* is also useful if you want to encapsulate away the implementation details of a data type away from client code using it - typedef the "handle" type to void* in the header and define the actual type internally in the library.
Useful for avoiding ABI breakage across library versions.
I personally interpret it as the type "Any". Thanks for your video!
You're very welcome!
You also want to use this type when calling a general purpose function with a callback and void* userData parameter. For example, if you use some kind of network callbacks, you want to access some shared state and mutate it some way. As long as you know what type was passed under void*, you can safely reinterpret cast it into target type and use this piece of memory
For me an easier way was to look at it like this: a pointer never has a type. A pointer is a memory address, nothing more. No matter if it is int* or void*, it is a memory address. Period.
The only thing you are doing by putting a type in front is sort of “hinting” to the compiler what the size is of the data that lives there, so when you do dereference it, the compiler knows how many bytes to get. It is more accurate as well than saying you have an int pointer. A pointer is a memory address, not a type.
That's how I see it as well since the CPU doesn't have any way of knowing what is there. For example, when you write char* foo, the compiler is essentially making a promise to you that it will only ever load from that address one byte at a time. Same with "unsigned int x = 3;", the compiler promises that it will only use unsigned operators on this variable. The CPU doesn't know or care what you or the compiler intended, it's not that smart
“What is a pointer?” - A thing that tells you where something else is. Int pointer tells you where an int is.
“Why use pointers?” - Because sometimes you want to talk about where to find something rather than the thing itself.
FYI:
- passing structs/unions to functions by value is acceptable, a pointer is not needed. It makes a copy when you do this, of course.
- malloc does not require a cast. Void pointer never needs a cast.
- void pointer should be used when the underlying type is unknown at compile time.
i love that you re-explain pointers, good for us that use reference only languages but love c/c++.
A project I work with regularly seems to think that void pointers exist as a means of documenting and explaining the code.
When you use them everywhere, anybody that uses your code needs to figure it out really well to use it, so it becomes self-documenting once they spend the time analyzing it, so you can save time writing documentation.
In general, void pointers are used when you have to work with memory addresses and deliberately say "I do not care at all what the memory here means". For example, malloc() returns a void pointer, because the function can not know what the memory you allocated means.
i once found a great usecase for void ptr when i wrote an event system, cause every participant is able to send a notification to the other members with the event id and an additional optional const void ptr.
that ptr is there to give the participants additional information about that event and it could be anything so it's void
Are you sure that at 5:45 the compiler will throw a warning? If am not wrong it will throw an error, but only with C++, it is totally fine to implicit covert a 'void *' to another kind of pointer in C.
Yeah. void * can implicitly be converted to any other pointer type in C and there should be no warning
He is confusing C and C++. In C you shouldn't cast the result of malloc.
Just a little remind: what you said at 2:13 isn't totally true.
You can pass a strct type variable to a function by value.
This is an example.
#include
struct Person {
int id;
char name[64];
};
void print(struct Person p)
{
printf("p.id=%d p.name=%s
", p.id, p.name);
}
int main()
{
struct Person pp = {1, "John"};
print(pp);
return 0;
}
Anyway, still learn a lot from this video. Thank you.
It's great for making generic lists as well. If you build that list that can take any data type then the void pointer is great to use.
Passing structures by value is supported in C89 and later. Generally not a great idea, but it is supported.
One of the most compelling reasons is to allow side effects on the object used as an argument; that is, let the function directly modify the callers object. Pointers to void allow type agnostic treatment of objects. They must be cast to a pointer to a known type before being used. I have written a number of libraries that allocate an object for context information specific to the thead using the library. Rather than exposing the structure to the client code, a handle to the context is returned by the init function, and the handle is of a typedef of void*, making it opaque to the caller. In that context, 'void*' is being used for data hiding.
There are many other good uses of void pointers.
I learned C long before the standards committee was formed, had to deal with the implementation differences between the K&R compiler, PCC, and various vendor implementations. I even worked for one of those vendors (MWC). The standardization of the core language is what ensured its long reign as the systems programming language of record.
The void* -> *Person typecast warning is only for C++.
In C, this is perfectly valid, and doesn't trigger any warning.
a pointer does NOT always contain the address of another variable. It can point to raw data or a function or unintialized memory or even nothing at all.
easier to say that it contains an address in virtual memory, where zero is defined as no address instead of first address
at the 5:56 mark, you state that you need to cast the pointer type. It looks like you're using standard C, so that's not actually true. In C the void * type is compatible with all pointer types, so no casting is required. If, however, you're using a C++ compiler, because of the added type safety of the C++ language, you'll need to use a cast to your resulting pointer type, but if you're using C++ then you should probably be using new, and delete, and templates, to avoid the use of void *.
And compare to nullptr, not NULL
The logical question is actually "Why do non-void-pointer data types exist?" At runtime all software is basically just a list of void pointers to data and instructions in memory.
The answer is of course convenience for our human brains, but those abstracted high-level types don't easily handle every corner case so the rawest of raw pointers is retained (as is the ability to include a block of assembly.).
The answer is for compilation really. I'd argue runtime typing is also just bits doing computation where the logic of the program makes sense of itself during runtime. That's just an extension of what's happening at compilation.
Also the reason why we have unsigned integer types rather than:
"unsigned less than"
"unsigned greater than"
"unsigned multiply"
"unsigned divide"
@@williamdrum9899 Those exist at the machine code level. A register is just a group of anonymous bits, the way an operation is performed on those bits is what gives it signed or unsigned properties.
@@williamdrum9899 Maybe that's what you were getting at.
@@mytech6779 Yeah that's what I was trying to explain but I didn't know how to phrase it
Note that even in C++, where you can use the new-operator, there's still a place for void: accessing raw memory, like you often do in embedded or low-level programming. And that's pretty much the only real use in C++ for void.
I learned my pointers on old PLCs. Void pointers are the only thing they have, you have to cast that pointer into a type every time you dereference it.
I was really confused when I learned about typed pointers in C. „How does an address have a type???“ :)
In a program I wrote, I needed a linked list of variables, specifically, a linked list of many different structs, these structs had some things in common for reading purposes. Without void*, i wouldn't have been able to make that, since the type of variable isn't known beforehand.
Finally someone who writes if (NULL == some_ptr) instead of the more usual other way around with NULL being on the right. I always do it like this, but my colleagues always complain about it, no matter how much I try explaining to them that if you write it like this, it won't compile if you accidentally only use a single = instead of the double ==. Unlike "if (some_ptr = NULL)", which will compile no problem but will not do what you intended...
Of course, a simple "if (some_ptr)" would do the trick, but I rather be explicit (one colleague is known for writing it like this and then putting a "// make sure that it's not NULL" behind it -duh), especially since it shouldn't make any difference in the compiler output.
I really don't like how C lets you assign things inside an if statement. If they just removed that feature there would be no debate!
@@williamdrum9899 there are a few things that C lets you do, that fall in this category.
You can do them, your source code looks shorter, it introduces problems and still your binary isn't any smaller.
But that's never going to change. I also never use anything thdt isn't either a boolean variable or expression in an if statement. So if I check that a pointer is not NULL, I explicity write it instead of if(some_ptr). It will generate the same machine code anyway.
i have used c since 1988. its still thenbest language out there for anything that touches hardware. and i jave done tons of embedded stuff. sure assembly is slightly better when dealibg with the lowest levels but c is the way to go for the majority of the work.
I love how you glossed over the arrow operators and "memset" to then stop and explain what the dereference operator does lmao
Void * can also be used as a handle to prevent the user of a module to have direct access to the object it represents. The type of the data structure is not exported in the header file, but only know in the source file or precompiled object file or library.
2:11 you still can pass person p by value (and return it...), we use pointers for changing data "in place" so passing pointer you change data that pointer points to instead of making copy of a struct inside function
Was about to comment on that too. That part was a bit confusing.
I found it odd that he said that. I also noticed the sneaky removal of memset which would have shown a compile error. He could have done memset(pPerson->name, ...) or memset(&pPerson->name[0], ...)
Another good place to use void pointers is in functions that dont care what kind of type you are sending to it. lets say you have a function that is void send_data_UART(void * data, size_t data_size) In this function if the data type is in a byte array (uint8_t *) or another struct, the callee would need to dereference their pointer manually, if its not already a byte array. It makes the callee code hella messy. But the send_data_UART does not care what date you give it - it wants a pointer and it will send all the bytes after that pointer.
I am working on a project where someone back in the day decided to have exactly those UART routines take a uint8_t* for the data instead of a void*. But of course what you actually put in there are some structures that are relevant for the protocol that you are using. Way too much casts there that could have been avoided if they had used void*.
@@Colaholiker Yep. STM's HAL is full of this. Its one of its more annoying qualities.
@@Nik930714 I know a lot of folks rave about the code generation in STM Cube. While it is nice to get you started, I think the quality of that code is atrocious. If I ever were to work on a STM platform, I would likely have something generated by Cube and then re-do it from scratch, not just ditching their code templates but also Eclipse as an IDE. It still can't Ctrl-Tab between editor tabs in 2023. Please leave my computer. LOL
I first ran into these when learning to use FreeRTOS, to pass arguments to tasks. It was weird the first time I read about it but it made sense to realise it was basically used to make the type for the function like an "any type". You'd make the memory. Cast it to a void* to pass it to the task, and then cast it back to the original type in the task
In c you can assign any pointer to a void pointer, you don‘t have to type cast it. And in C you SHOULD not cast, you do it for the sake of C++
Years ago I used void* to create a generic swap function. Pass in the two memory locations and the size to be swapped.
At 2:13 you claim, that you could not pass the structure Person by value ... but this is actually wrong.
While C-language does not pass arrays by-value, it does indeed pass structures by value if you use a
struct variable rather than a struct * variable (as argument to a function). This of course will make the
code rather slow (and might have the side effect, that the called routine is only making changes in
the copy). However, my point is, C does indeed allow to pass structures by value.
Weird. I only program in assembly for retro hardware and I've never felt the need to pass an entire struct by value (or, for that matter, anything bigger than 2 registers)
Wow, that was the most reasonable way of explaining pointers I have ever heard :D
Useful for implementing callbacks, when you want separate compilation. qsort is an example. Though std::sort is faster for any particular type, it has to be instantiated for each type it is used for leading to more generated code. With void*, we can avoid that.
You do not have to cast the return of malloc. It is better no to cast it because a void * is designed to be cast to anything. You may hide type problems by doing so.
I would welcome if you would correct that statement in the video.
I consider void * to be one of the useful features of C and C++, it allows you convert between variable types like char and int and allows you to hide information from user of your software.
Also the fact that you can write a single function to de operations on different types makes it an indispensable type
When I was first learning C++ I was confused by * because it is both an operator and a type specifier. I find code more readable when the * is attached to the type when used as a type specifier, e.g. char* vs to the variable/value when used for referencing it, e.g. *Value, unfortunately the common notation is to always attach it to the value for whatever reason.
Isn't it attached to the variable name because declaring like this:
int* a, b;
actually declares one pointer and one integer, not two pointers?
@@EdKolis Yes, also attaching the star to the variable name at declaration was often used, because declaration looks the same as dereferencing.
@@EdKolis You are right it does, while const and static both apply to both variables, which bring up the question of why would they do something like this?
Yeah, I for some reason feel like ''char* variable'' makes more sense than having the * at the variable itself, but the language certainly has an expectation that it's meant to be the latter of the two.
Declaring multiple pointers requires ''char *var1, *var2'' for example, and declaring a fixed-sized c-array as a parameter is ''char (*variable)[T]''.
The parser doesn't care in most situations, but it is a thing lol.
Came here to say this, the type is "char*" and should be written as a single word, attaching the asterisk to the variable's name feels weird. ...And back up the ram bus (vroom vroom), going "int* a, b;" _doesn't_ have both variables be of type int-pointer??? Wat.
This is perfect timing, in my OS class we started modifying an OS and I saw a bunch of void* and didn’t know what it was
pnt is when you call a subroutine that in assembly has an offset + base address like $06[R0], the pnt is the base address in R0.
C makes sure you understand what your'e doing so it only allows similar pointers, the type has nothing to with the size of a pointer as that is always the system size (like 32bit or 16bit),
but as it's just a raw 32/16bit value, passing along anything could work in theory, and you use void to override this check.
Void* can be used to make the type opaque. They'll just recreate a new variable at the start of the function.
Like windows window handle and other stuff in C.
Real var = (cast) void_argument
----
Also, it's for generic types. You still need to cast it. But it's a way to say: This can have multiple types.
Like for malloc. It returns an address. Just that. Hence, it has no type.
You don't need to cast the return value of malloc and it's considered a not-so-good practice to do so in C.
In simple words, a void * pointer is a pointer that can point to ANYTHING.
Generally said the you use the void* type everywhere where you would use generics in higher level languages. But you leave the user to cast those void* to the right types, which is what the compile would do when using generics.
There is a very good reason to use an int* , e.g., if you want to return more than one integer from a function call. You can return one as the return value and store the value of the other one in the supplied int*. It's a fairly common idiom, I believe. I can't find an exact example right now but strtol() from cstdlib uses a char** for this exact purpose.
Excellent presentation of a very commonly misunderstood topic. Saving this channel and will be recommending videos from it to newer devs, thanks 3L!
6:18 yoda notation 🔛🔝
"A void* represents a pointer to anything" is all I think about when I see it. Knowing that, I can typecast (as in "(T*)p") and all is well i n the world, imho.
Hi there, first of all sorry for my english, second of all i love this man's channel, third, i have a doubt and i'm a begginer in C, please don't be rude, here it goes:
in 2:06 he said that you could not pass a struct by value to a function because it is greater than the architecture word size, but you actually can, the code compiles and works. So it is a thing about performance? i mean i know a pointer size is less than a large struct size so i it makes sense to pass only the reference, but when you pass a variable smaller than the word size it doesnt matter passing it by value because a pointer max size is the same as the word size? for me it makes sense because i did read somewhere that the word size of an architecture is the same as the directions bus, so the word size full of 1 is the same as the max memory register address, but i don't really know if i am wrong. Am i understanding this well? or am i completely wrong? thanks in advance!
void pointers are pure addresses which make no presumption about the data type. It allows you to pass the address without the type. This makes them dangerous for many things. Some APIs require them and some memory operations use them. They are generally to be avoided when possible. There are other design patterns which can be used in their place most of the time. (Queue to a structure in a thread pool.)
There are a few exceptions. These generally involve cases where you only know the address and will never care about the type. A cache line request for example can be void pointer. A stream parser can be a void pointer, until you discover the type from the stream. So void pointer allows you to communicate things.
As other mention data structures use them sort of like templates.
I really like void pointers. It's fun to play with them. Tho they are pretty easy to fuck up with, still they are fun. They basically allow you to make anything possible even in runtime.
well a pointer to an integer is still useful because your program can keep track if the value changes, even if there's no memory savings versus passing it by value
I had to work on a windows driver once where the top level called lower level routines all with void pointer parameters. The original programmer believed that since the lower level didn't specify any types for its parameters, the driver was more portable. Ugghhh.
Thanks for the video. I hope next time you explain Function pointers.
I almost never turn on notifications to channels I'm subscribed to but I'm making an exception. Thanks for the great content.
Simple analogy for the TS homies here: void* = any/unknown from TS
unknown even has the same semantics - you can pass it to any other type without explicit typecast but you have to make a typecast when you need to work with it.
I've never found the very concept of pointers particularly hard to grasp. What I struggled with when learning this topic is its C syntax.
you program on vim without autocompletion? how do you include external libraries without knowledge whether it's inside folder whether its uppercase?
You did a much better job explaining this than Zack did trying to explain his channel name 😂
Who's Zack?
@@yeetmaster6986 exactly
@@yeetmaster6986Zack Freedman a.k.a. void star labs
I've seen void pointers used with runtime type info schemes and internally for variant types.
Nice little refresher video!!!
Pointers have nothing to do with word size, it’s about memory allocation, specifically allocating on the stack or the heap.
Very cute; the outro demonstrates that even experienced programmers such as yourself have trouble with pointers sometimes ;)
That was an incredibly informative video! Thank you so much! I'm a little lost about the working of the free() function. Could you, or perhaps someone else, shed some light on how free() knows how many bytes it needs to free? It takes in a void pointer and still somehow figures out the length of the memory it needs to release 🤔
The OS usually keeps track of the memory block you request with malloc. If you tell the OS that you no longer need the block it gave you, starting at that address, it will go into its own allocation table and clear the address and number of allocated bytes out of that table. (It doesn't actually delete any of the data that was in that block of memory, the block of memory is just now free to be reallocated by another malloc call. So if you didn't set that data starting at that address to 0, then whoever gets it allocated next can read what you had saved there before)
I think void *ptr; makes the most sense when you think about weird architectures. Okay you can say that char will be the smallest addressable on that machine that maybe does not use bytes but lets say 10 bit somethings - but maybe the machine has opcodes to access 16 bit data when they are from a different memory region and step by 16 bits properly when indexing.
In that scenario void is much better solution to hide the type of the thing (like a handle that is a pointer to this or that region!) because properly writing it out is impossible - yet every use of it you should cast it properly to proper pointer type and in theory you can make the compiler seamlessly handle all this.
That being said this is very rare - but for example there are indeed microcontrollers where flash can be written-to but with 12 bit values and they also have regular (but smaller) nvram to write to with 8 bit slots... So even though the usual solution is to just make some kind of API that expects you to write 16 bit values with the topmost 4 just ignored, the language itself is actually made to handle also this weird shit situation "well" - by that I mean "somewhat logical"...
If I understood it correctly the short version of what a void pointer is.
A pointer to an unknown type variable.
i love you man, explain c language not c++, i saw the pointer in book i learn them but yeah wasnt deep learing at all
thank you man i got it fully now
Can you please make full c programming for beginners tutorial? That would be very helpful.
I can't think of many instances where I've legitimately used void pointers in my code. Even if I don't know what a blob of memory contains, I usually specify that the smallest unit of data is an unit8_t (because I need at least some sort of reference to work from). A void pointer to me basically means that either the sender or the receiver doesn't know anything about the data, not even what the size of the smallest (or largest) discrete value represented within is. Not even if there is any data there at all. It's like a random GPS coordinate: What's there? I don't know, it could be a business, a house, a tree, a single molecule of water, or nothing at all. What should I look for? I don't know. What should I do what I get there? I don't know. Should I go there at all? I don't know. You should know, it's not my responsibility to know. It's a void pointer.
2:44 "because Person is such a large structure, we have to....."
Could have added the explanation for "have to" in there...
I use void pointer mostly to store raw RGB array of "opengl" texture, that way I store direct address of texture instead of memcopying it.
A good explanation, will use it as a material for my lessons!
I view void pointers as a "pointer to the data type of the address bus granularity", in most cases a byte.
We should all take a minute to send hateful thoughts towards the person that made the C++ syntax. I was a C-programmer but stopped programming C when C++ came. I mean, come on??? "cout
Very clear explanation, thank you.
Love your stuff! Just want to point out that IMO the first half of the video explaining pass by value may be a little confusing to people who are unfamiliar.
Your explanation sounds like youre saying “if a value being passed to a function is within a certain byte size it will pass in the ACTUAL object” which is not true. C always passes everything as a copy. Its just that it is feasible and efficient to pass values as copies into functions when they are simple small data types, whereas with bigger data structures C doesn’t allow it.
To me, the best way to understand pointers is simply “C passes by value copying, what do you do if you want the actual thing instead of a copy? Pointer!”
They exist because types are a fiction we made to help us do our work. A lack of types is the default state, and C comes from a time before everyone demanded to live in walled gardens. That's not to say C doesn't have bullshit problems that compiler vendors exacerbate by being rules lawyering dicks, but that's a conversation for another time.
"Pointer" was a hardware concept historically. It was an "address" pointer. No concept of a type was associated with the pointer. Any type information was part of the thing located at the address pointed to. The concept of associating a type with a pointer is a mid or high level concept, not a low level concept.
You can be in need of a pointer to an int if you want to be sharing your changes of the value with others.
void* malloc is one of the better examples of why generic/templated functions are so useful in a modern language. Something like (MyType*)malloc(sizeof(MyType)) is so much less useful than malloc() where the assignment hints to the compiler the sizeof and cast arguments.
There's actually a way to do this better in C:
Person* myPerson = malloc(sizeof *myPerson)
Also the feature you're taking about is type inference, not generics
@@ex-xg5hh I believe it is still generics, because you'd have a single malloc function that must operate over arbitrary types. I guess in C-like languages you could use function overloading and type inference to select the appropriate overload, but that seems like the lesser of the two choices.
Can be used in lib's that are dependent one of another, you can not include the header file of one lib in to another lib and vice versa at the same time, so you don't include the header file in the header file but in source file and pass void pointers and cast them, that way you avoid recursive inclusions.
for that use case, ifdef guards and pragma once exit
And try to always avoid including headers in headers. That can cause confusion and include unnecessary code. ( unless it's a file without any actual code )
An ifdef guard is when you surround your headers with
# ifndef HEADER_NAME_H
# define HEADER_NAME_H
/* header content */
# endif
this prevents the header from being included twice, since the first include defines the macro, and in subsequent included versions the preprocessor will skip over the contents.
# pragma once
at the top of the header also prevents it from being included multiple times, but does so much more cleanly.
2:22 I could feel the hesitation in going for pP.
It just means it's a pointer but don't worry about types being compatible.