1

My question is driven by the need to correctly pass a double variable via a void * function argument. I ask because on my machine sizeof(void *) is 4, and sizeof(double) is 8, casting one to the other seems like it should result in a problem, but my compiler (CLANG, with ALL warnings on.) gives no indication of a problem, and the code seems to work fine.

Note that I have seen this, and this. They have similar component words in their titles, but do not answer this specific question.

Would the following lead to a strict aliasing violation error?, or undefined behavior?

// some calling function
double a = 0.000234423;
func1(&a);

...

void func1(void *var)
{
    double a = *(double *)(var);
}
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • 8
    Your 4-byte pointer points to an 8-byte object. What's the problem? – underscore_d Jan 13 '20 at 16:30
  • 1
    Your question says that you want to store a double value in the value of the pointer, but your code says you want to store a pointer to a double in your pointer. Which one is it? Only one makes any kind of sense, but you should still be specific. – Mad Physicist Jan 13 '20 at 16:31
  • 1
    It's `sizeof(double*)` that's pertinent, not `sizeof(double)`. Beyond the size, casting a pointer to `void*` and back is safe. – ikegami Jan 13 '20 at 16:33
  • 3
    @MadPhysicist: I don't see anywhere where it literally says to store a double **value**. The problem is how you pass a double **variable**, and the basic answer boils down to "pass by reference" because "pass by value" doesn't work. – MSalters Jan 13 '20 at 16:34
  • @MadPhysicist - The intent in my question was to ask how I can pass one via the other. My intent is to use a void * argument so I can pass several different types. – ryyker Jan 13 '20 at 16:35
  • 4
    It's fine as long as you don't do the dirty hack ie cast a value to a pointer, then have the callback function convert back from pointer to integer. Apparently this is a common hack in various thrashy pthread applications (because they fear the original value will go out of scope if passed by ref). – Lundin Jan 13 '20 at 16:35
  • 2
    *`sizeof(void *) is 4`* So? What if that `void *` started as a `char *` that pointed to a 124,513 byte `char` array? – Andrew Henle Jan 13 '20 at 16:36
  • 1
    `My question is driven by the need to correctly pass a double variable via a void * function argument.` - Can you specify that reason in more depth. Why do you need to do so? What blocks you from making the parameter of type `double*`? – RobertS supports Monica Cellio Jan 13 '20 at 16:43
  • 1
    "casting one to the other seems like it should result in a problem" --> Code is **not** casting between `void *` and `double`. Instead code is converting between pointer types `void *` and `double *`. `sizeof(double)` is irrelevant here . Post unclear. – chux - Reinstate Monica Jan 13 '20 at 16:44
  • @RobertS-ReinstateMonica - Yes, in my actual code I need to allow user to pass different types, which will be resolved in the called function, so I am using a `void *`. I was just trying to keep only the essential code to illustrate the problem. – ryyker Jan 13 '20 at 16:47
  • @underscore_d - Lol, (on your first comment.) This seems obvious now. I coded a couple of variations this morning that _did_ give me the `strict aliasing` error, and think I got a little gun-shy on believing this was OK. Thanks for your input. – ryyker Jan 13 '20 at 16:50
  • @ikegami - _It's sizeof(double*) that's pertinent, not sizeof(double)_. Thanks. – ryyker Jan 13 '20 at 17:00
  • @RobertS-ReinstateMonica - Thanks for the edit to title. Its accurate. – ryyker Jan 13 '20 at 17:31
  • @ryyker I think the rationale behind your question is a very good and interesting idea, but it was a little "bad" or confusing incarnated. – RobertS supports Monica Cellio Jan 13 '20 at 17:40
  • 1
    @rykker I rolled back your question. I do understand that your initial question wasn't exactly what you wanted to ask but completely rewriting the question after answers have been posted is not a good idea. You invalidate those answers and things look strange for later visitors. Please add a new question instead. Thanks. – Support Ukraine Jan 14 '20 at 16:25
  • @4386427 - I debated whether I should have just asked a new question. I think your suggestion is good, and at some point I will post it as another question, and ping you when I do. Thanks for rolling this back. – ryyker Jan 14 '20 at 16:45
  • @4386427 - See new question (as you suggested.) [HERE](https://stackoverflow.com/q/59754887/645128) – ryyker Jan 15 '20 at 15:48

3 Answers3

8

The size of a pointer has nothing to do with the size of the element it points to!

A 4 byte pointer can without problem point to an element which is 8 bytes or 32 bytes or whatever size.

I ask because on my machine sizeof(void *) is 4, and sizeof(double) is 8, casting one to the other seems like it should result in a problem

Well, if you did cast a 4 byte pointer to an 8 byte double, it would be a major problem. However, that is not what your code is doing.

This is what the code is doing:

double a = * (double *)(var);
           | \--------------/
           |  This casts a void pointer to a double pointer
           |
           --> This dereferences the double pointer, i.e. reads the value of the pointed
               to element (aka the pointed to double)

As the void pointer was created from the address of a double, it's perfectly legal to cast it back to a double pointer and perfectly legal to read the value of the pointed to element. In other words - during the function call you have an implicit cast of a double pointer to void pointer and inside the function the void pointer is casted back to double pointer. Perfectly legal C code.

With an extra step your code is equivalent to:

void func1(void *var)
{
    double *pd = (double *)var;
    double a = *pd;
}

which makes it a bit more clear.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • Re “The size of a pointer has nothing to do with the size of the element is points to!”: That depends on the C implementation. C implementations may have pointers of different sizes for different types. – Eric Postpischil Jan 13 '20 at 17:20
  • @EricPostpischil: One of the things that irks me about the C Standard is that it fails to recognize common categories of implementation that can and do, at essentially zero cost, offer stronger semantic guarantees that might be expensive or impractical on others. On the vast majority most platforms, all data pointer types have the same size and representation, and all bytes of a null pointer's representation will be zero. Code that makes such assumptions would not be portable to platforms where they don't hold, but programmers shouldn't have to make portability concessions for platforms... – supercat Jan 13 '20 at 19:56
  • ...upon which nobody would be interested in running their code. People who have to write code for use on weird platforms should be aware that commonplace assumptions won't hold on them, but the 99% of programmers whose code will never run on such platforms shouldn't need to be paranoid about such issues. – supercat Jan 13 '20 at 19:57
4

The normal solution is indeed to pass the address of the double variable, so a double*. That has a size that's no bigger than a void*.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 2
    And that's what they're doing. I think the problem is just a misconception that the size of the object matters, but it's the size of the pointer to it that does. This is fine. – underscore_d Jan 13 '20 at 16:31
  • @underscore_d: Yeah, I realized the wording could be more explicit. Added "indeed". – MSalters Jan 13 '20 at 16:32
  • 2
    The _size_ of pointer types is irrelevant. What is important is that conversion from `double *` to `void *` to `double *` is well deifned and works as needed. – chux - Reinstate Monica Jan 13 '20 at 16:48
  • @chux-ReinstateMonica: There's a theoretical argument that on some systems, a `char*` might need point to individual bytes inside a larger machine word, where the native addressing does not have this capability. This means `char*` has to be as large as a native pointer plus a a byte offset, and therefore `void*` as well but not `double*`. In practice, everyone uses octets. – MSalters Jan 13 '20 at 16:53
  • As I mentioned in one of my comments above, I had tried somethiing this morning that resulted in a `strict aliasing` error and so had a little lack of confidence on this, even though my compiler seemed to sign-off on it. Thanks for the clarity in your answer. – ryyker Jan 13 '20 at 16:53
  • 1
    @ryyker: What makes the cast legal is that it exactly reverses the implicit cast in the call. But C doesn't enforce that. `func1("pi")` also compiles. – MSalters Jan 13 '20 at 16:58
  • I'm not sure I understand this part: "That has a size that's no bigger than a void*." AFAIK the standard doesn't say anything about the size of pointers of different types. It only says that a pointer to type T can be converted to void pointer and back to a pointer to type T. Or am I missing something? – Support Ukraine Jan 14 '20 at 16:32
  • @4386427: Very strictly speaking it's the size of the set of representable values, every ` double*` can be represented by at least one `void*` but not necessarily vice versa. In practice, alignment means that the set of possible `void*` values is much larger but the `sizeof` is identical as the lower bits of a ` double*` are always zero. – MSalters Jan 14 '20 at 17:10
1

If a function that receives an argument of type void* needs to receive a double value, one can define a structure, function, and macro:

struct doubleWrapper { double d[1]; };
struct doubleWrapper doWrapDouble(double d)
{      
  return (struct doubleWrapper){d};
}
#define wrapDouble(x) (doWrapDouble((x)).d)

and then use that function at call sites that need to pass a double value:

void functionTakingVoid(int mode, void *param)
{
  if (mode==0)
  {
    double myDouble = *(double*)param;
    ...
  }
}

void passDoubleToFunctionTakingVoid(double myValue)
{
  functionTakingVoid(0, wrapDouble(myValue));
}

Under C99, wrapDouble macro will return a pointer to a double whose lifetime will extend through the evaluation of the enclosing full expression, including function calls made thereby.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • I had not heard of `wrapDouble`. By the way, [this question](https://stackoverflow.com/q/59754887/645128) has more conversation on a newer version that includes more detail on the same topics – ryyker Jan 15 '20 at 22:30
  • 1
    @ryyker: It is a macro which calls a function, given above, that returns a structure, also given above. – supercat Jan 15 '20 at 22:32