Why can't we use a void* to operate on the object it addresses
Asked Answered
T

5

4

I am learning C++ using C++ Primer 5th edition. In particular, i read about void*. There it is written that:

We cannot use a void* to operate on the object it addresses—we don’t know that object’s type, and the type determines what operations we can perform on that object.

void*: Pointer type that can point to any nonconst type. Such pointers may not be dereferenced.

My question is that if we're not allowed to use a void* to operate on the object it addressess then why do we need a void*. Also, i am not sure if the above quoted statement from C++ Primer is technically correct because i am not able to understand what it is conveying. Maybe some examples can help me understand what the author meant when he said that "we cannot use a void* to operate on the object it addresses". So can someone please provide some example to clarify what the author meant and whether he is correct or incorrect in saying the above statement.

Transoceanic answered 14/4, 2022 at 8:58 Comment(4)
Short answer: In c++ we almost never need void*. Templates largely get rid of places where we have an unknown type, typed runtime helpers such as std::copy get rid of places where we'd use CRT functions that have void* parameters like memcpy. Any new piece of C++ code which isn't deliberately interfacing with C or doing low level packing/manipulation which uses void* should be viewed with suspicion.Ryun
type controls Pointer Arithmetic. Without a known type, then ptr++ and ptr-- are meaningless and undefined. With a char* pointer, ptr++ advances 1-byte to the next character, for int*, ptr++ advances by 4-bytes to the next integer. With a class object, say myclass*, ptr++ will advance sizeof (myclass) bytes to the next myclass object. With void* nothing tells the compiler how big the pointed to object is. Now there are other ways that void* is usable, but this is the general reason why.Invisible
"4 bytes to the next integer". It's really sizeof(int), which often but not universally is 4. In the past you also saw 2. And double isn't a class either, but incrementing a double pointer also increments it by sizeof(double) bytes. The rule is absolute. Even for char*, incrementing it will increment it by sizeof(char), its just that sizeof(char) is defined to be 1.Kooima
Note that void* cannot be used to store a member function pointer. So technically, "Pointer type that can point to any nonconst type" seems to me to be incorrect to me.Chary
B
2

Yes, the author is right.

A pointer of type void* cannot be dereferenced, because it has no size1. The compiler would not know how much data he needs to get from that address if you try to access it:

void* myData = std::malloc(1000); // Allocate some memory (note that the return type of malloc() is void*)

int value = *myData; // Error, can't dereference
int field = myData->myField; // Error, a void pointer obviously has no fields

The first example fails because the compiler doesn't know how much data to get. We need to tell it the size of the data to get:

int value = *(int*)myData; // Now fine, we have casted the pointer to int*
int value = *(char*)myData; // Fine too, but NOT the same as above!

or, to be more in the C++-world:

int value = *static_cast<int*>(myData);
int value = *static_cast<char*>(myData);

The two examples return a different result, because the first gets an integer (32 bit on most systems) from the target address, while the second only gets a single byte and then moves that to a larger variable.

The reason why the use of void* is sometimes still useful is when the type of data doesn't matter much, like when just copying stuff around. Methods such as memset or memcpy take void* parameters, since they don't care about the actual structure of the data (but they need to be given the size explicitly). When working in C++ (as opposed to C) you'll not use these very often, though.

1 "No size" applies to the size of the destination object, not the size of the variable containing the pointer. sizeof(void*) is perfectly valid and returns, the size of a pointer variable. This is always equal to any other pointer size, so sizeof(void*)==sizeof(int*)==sizeof(MyClass*) is always true (for 99% of today's compilers at least). The type of the pointer however defines the size of the element it points to. And that is required for the compiler so he knows how much data he needs to get, or, when used with + or -, how much to add or subtract to get the address of the next or previous elements.

Bulimia answered 14/4, 2022 at 9:26 Comment(8)
Since this is C++, I'd use std::malloc, and use static_cast instead of C-style cast.Strikebreaker
@Strikebreaker Updated with alternative syntax. I personally prefer the C-way, because it's shorter ;-)Bulimia
@Bulimia Take for example: int i;void* void_ptr = &i;. Now you mentioned that a void* has no size. So how can a int* on the right hand side that has some size be implicitly converted to a void* that has no size? I mean, int* has some size. But void* has no size. So how is it going/converting from some size(on the rhs) to no size(on the lhs) implicitly?Transoceanic
@Albert void* type has size, it's a pointer. void doesn't have size, so if you have void* ptr;, sizeof(ptr) is fine, but sizeof(*ptr) is not. Maybe answer could e edited to be more clear about this...Strikebreaker
Added another explanation. It's a bit confusing at first, but you just have to make yourself clear that the size of a pointer variable and the size of the thing that pointer points to are two completely different things.Bulimia
@Albert: It's always easy to throw something away (in this case, the size of i). Getting back something that you've thrown away is a lot harder.Kooima
@Bulimia "This is always equal to any other pointer size" I've asked a follow up question regarding this: Do all pointers have the same size in C++. Also, thanks for the input. And your edit is much better since it is not less confusing.Transoceanic
@Albert Saw that question, yes. Most of the time this is not relevant, though.Bulimia
M
2

My question is that if we're not allowed to use a void* to operate on the object it addressess then why do we need a void*

It's indeed quite rare to need void* in C++. It's more common in C.

But where it's useful is type-erasure. For example, try to store an object of any type in a variable, determining the type at runtime. You'll find that hiding the type becomes essential to achieve that task.

What you may be missing is that it is possible to convert the void* back to the typed pointer afterwards (or in special cases, you can reinterpret as another pointer type), which allows you to operate on the object.


Maybe some examples can help me understand what the author meant when he said that "we cannot use a void* to operate on the object it addresses"

Example:

int i;
int*  int_ptr  = &i;
void* void_ptr = &i;
 
*int_ptr = 42;  // OK
*void_ptr = 42; // ill-formed

As the example demonstrates, we cannot modify the pointed int object through the pointer to void.

so since a void* has no size(as written in the answer by PMF)

Their answer is misleading or you've misunderstood. The pointer has a size. But since there is no information about the type of the pointed object, the size of the pointed object is unknown. In a way, that's part of why it can point to an object of any size.

so how can a int* on the right hand side be implicitly converted to a void*

All pointers to objects can implicitly be converted to void* because the language rules say so.

Mallorie answered 14/4, 2022 at 9:31 Comment(7)
I think you can't determine the runtime type of a void pointer. I would expect that dynamic_cast<> of some void* is not possible.Bulimia
@Bulimia Indeed, you cannot determine the type based on the pointer to void alone. But that doesn't man that you cannot convert the pointers type. You just need to know what type to convert to.Mallorie
@Mallorie In your example, when you wrote: void* void_ptr = &i;, so since a void* has no size(as written in the answer by PMF), so how can a int* on the right hand side be implicitly converted to a void*? I mean, int* has some size. But void* has no size. So how is it going/converting from some size(on the rhs) to no size(on the lhs) implicitly?Transoceanic
@Mallorie Ok, my confusion came from the statement "A pointer of type void cannot be dereferenced, because it has no size."* Your edit clears my confusion. So if i understand it correctly does this mean that all pointers(including void*) have the same size for a given system(compiler, architecture etc).Transoceanic
@Albert No, that's not necessarily true. It's quite common for all pointers to have the same size, but it's technically possible for pointer types to have different sizes.Mallorie
@Mallorie Ok,but all pointers must have at least some size right? +1 for the clarification.But i think if it is technically allowed for pointers to have different size then we will face a problem. For example say we've: int i =0; void* ptr = &i;. Now, suppose void* and int* have different sizes.Then, when implicitly converting a int*(say its size is 4 bytes) to the void*(say its size if 2 bytes), there will be some loss while converting the rhs to the lhs. So, i think they should have the same size. Please correct me if/where i am wrong. Or should i ask a separate follow up question.Transoceanic
@Albert Furthermore, all objects have a size. Pointers are objects. when implicitly converting a int*(say its size is 4 bytes) to the void*(say its size if 2 bytes), People who write compilers are smart and they know that this would be a problem, so they wouldn't pick such sizes.Mallorie
I
1

void * is basically a catch-all type. Any pointer type can be implicitly cast to void * without getting any errors. As such, it is mostly used in low level data manipulations, where all that matters is the data that some memory block contains, rather than what the data represents. On the flip side, when you have a void * pointer, it is impossible to determine directly which type it was originally. That's why you can't operate on the object it addresses.

if we try something like

typedef struct foo {
   int key;
   int value;
}              t_foo;

void try_fill_with_zero(void *destination) {
    destination->key = 0;
    destination->value = 0;
}

int main() {
    t_foo *foo_instance = malloc(sizeof(t_foo));
    
    try_fill_with_zero(foo_instance, sizeof(t_foo));
}

we will get a compilation error because it is impossible to determine what type void *destination was, as soon as the address gets into try_fill_with_zero. That's an example of being unable to "use a void* to operate on the object it addresses"

Typically you will see something like this:

typedef struct foo {
   int key;
   int value;
}              t_foo;

void init_with_zero(void *destination, size_t bytes) {
    unsigned char *to_fill = (unsigned char *)destination;
    
    for (int i = 0; i < bytes; i++) {
         to_fill[i] = 0;
    }
}

int main() {
    t_foo *foo_instance = malloc(sizeof(t_foo));
    int   test_int;
    
    init_with_zero(foo_instance, sizeof(t_foo));
    init_with_zero(&test_int, sizeof(int));
}

Here we can operate on the memory that we pass to init_with_zero represented as bytes.

Inconveniency answered 14/4, 2022 at 9:22 Comment(4)
That's not valid C++. The question is tagged C++, not C.Anzus
This is too low-level to be a pure C++ question and has to be illustrated accordinglyInconveniency
"Any pointer type can be implicitly cast to void * without getting any errors. " --> pointers to function converted to void * may lose information.Baron
You are correct. Technically, any type-information is lost when casting to void *Inconveniency
S
0

You can think of void * as representing missing knowledge about the associated type of the data at this address. You may still cast it to something else and then dereference it, if you know what is behind it. Example:

int n = 5;
void * p = (void *) &n;

At this point, p we have lost the type information for p and thus, the compiler does not know what to do with it. But if you know this p is an address to an integer, then you can use that information:

int * q = (int *) p;
int m = *q;

And m will be equal to n.

void is not a type like any other. There is no object of type void. Hence, there exists no way of operating on such pointers.

Swinish answered 14/4, 2022 at 9:20 Comment(5)
Shouldn't i use C++ style cast? Also, which C++ style cast would be suitable here.Transoceanic
Yes, this uses a C-style cast, but plain pointer types are a kind of C legacy anyway. The equivalent C++-Style cast would be static_cast<>.Bulimia
@Bulimia If i write int n = 5;void * p = &n; then will it be invalid or valid? Note that there is no cast done on the right hand side of the second statement as opposed to the example given in this answer. That is, is the cast on the right hand side necessary?Transoceanic
True, but that is a different discussion entirely. void * are a very low-level C concept and there is no need to drag any C++ and loose generality.Swinish
@Albert Yes, that's legal, because &n is of type int* and you're casting it to void*. This can happen implicitly, but the inverse cannot.Bulimia
L
0

This is one of my favourite kind of questions because at first I was also so confused about void pointers.

Like the rest of the Answers above void * refers to a generic type of data. Being a void pointer you must understand that it only holds the address of some kind of data or object.

No other information about the object itself, at first you are asking yourself why do you even need this if it's only able to hold an address. That's because you can still cast your pointer to a more specific kind of data, and that's the real power.

Making generic functions that works with all kind of data. And to be more clear let's say you want to implement generic sorting algorithm. The sorting algorithm has basically 2 steps:

  1. The algorithm itself.
  2. The comparation between the objects.

Here we will also talk about pointer functions.

Let's take for example qsort built in function

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

We see that it takes the next parameters:

base − This is the pointer to the first element of the array to be sorted.

nitems − This is the number of elements in the array pointed by base.

size − This is the size in bytes of each element in the array.

compar − This is the function that compares two elements.

And based on the article that I referenced above we can do something like this:

int values[] = { 88, 56, 100, 2, 25 };

int cmpfunc (const void * a, const void * b) {
   return ( *(int*)a - *(int*)b );
}

int main () {
   int n;

   printf("Before sorting the list is: \n");
   for( n = 0 ; n < 5; n++ ) {
      printf("%d ", values[n]);
   }

   qsort(values, 5, sizeof(int), cmpfunc);

   printf("\nAfter sorting the list is: \n");
   for( n = 0 ; n < 5; n++ ) {   
      printf("%d ", values[n]);
   }
  
   return(0);
}

Where you can define your own custom compare function that can match any kind of data, there can be even a more complex data structure like a class instance of some kind of object you just define. Let's say a Person class, that has a field age and you want to sort all Persons by age.

And that's one example where you can use void * , you can abstract this and create other use cases based on this example.

It is true that is a C example, but I think, being something that appeared in C can make more sense of the real usage of void *. If you can understand what you can do with void * you are good to go.

For C++ you can also check templates, templates can let you achieve a generic type for your functions / objects.

Lachrymose answered 14/4, 2022 at 11:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.