3

Very simply, is the following code safe/portable?

#include <stdio.h>
#include <stdlib.h>

int add(int *a, int *b)
{
  return *a + *b;
}

int main()
{
  int x = 2;
  int y = 3;

  void *ptr1 = &x;
  void *ptr2 = &y;

  fprintf(stdout, "%d + %d = %d\n", x, y, add(ptr1, ptr2));

  return EXIT_SUCCESS;
}

I've compiled this with -Wall -Werror and -Wextra and received no warnings; it seems to run fine.

John
  • 652
  • 7
  • 22

5 Answers5

4

It is safe:

C99

A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

But you need to make sure your original pointer type is correct.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
  • The fact that pointers may be converted does not guarantee that the resulting pointers may be used to access objects. Additional rules of C, such as those in C 2011 6.5 7 are needed to show that. Also, the 1999 standard is nearly two decades old and has been withdrawn. – Eric Postpischil Mar 19 '18 at 13:23
4

There's two things to consider:

  1. C allows the implicit conversion from a void pointer to any other object pointer type. So it's syntactically okay to pass those arguments.

  2. The type of the actual object being pointed to, and the type of the pointer the function expects, must satisfy strict aliasing constraints, otherwise your program is in undefined behavior land.

You are okay on both points. So your program is perfectly fine.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • ...but of course this turns off any type checking by the compiler. – Paul Ogilvie Mar 19 '18 at 11:57
  • 1
    @PaulOgilvie - Considering how much the compiler is meant to let slide already... I wouldn't call C *too* type safe personally. Still, it's part of its great power. – StoryTeller - Unslander Monica Mar 19 '18 at 12:00
  • 1
    Yes, C is like a chainsaw. You can get anything done with it. It used to be a convenient way to write assembler and the programmer knew what the compiler would generate. Now, it is turning into a horrible, semi high-level language, with its UB dogmas. – Paul Ogilvie Mar 19 '18 at 12:12
  • @EricPostpischil - But that's the point. Object pointer conversions are allowed. Accessing the stored value is not, unless the types align. Anyway, that's moot. I went in another direction. – StoryTeller - Unslander Monica Mar 19 '18 at 13:23
  • @StoryTeller: Alignment and access are not the issues. Compatibility is. “Align” and “access” are terms with different meanings in the C standard. – Eric Postpischil Mar 19 '18 at 13:24
  • @EricPostpischil - Of course access is the issue. [*"An object shall have its stored value accessed...*"](http://port70.net/~nsz/c/c11/n1570.html#6.5p7). I was also using "alignment" as a rhetorical device, not a formal term. So forgive the confusion. – StoryTeller - Unslander Monica Mar 19 '18 at 13:25
2

It's fine but by the skin of your teeth.

You are converting an int* to void* and that is converted back to int* as the pointer is passed by value into add.

If, for example, add took two pointers to double, say, then the behaviour of your code would be undefined.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
0

It's "safe" in that the behavior is well defined. Having a function declared as accepting void * is normal when it needs to deal with many different types of data; e.g. memcpy.

It's not morally safe. By passing void * you are making the compiler unable to check that the pointer you are passing points to memory that holds data in the form and quantity that you expect. It is then up to you to enforce this.

In your trivial example you can see that the objects are actually int, and everything is fine.

In general, however, where we pass void * to a function, we should also pass additional information describing the memory we are handing over in enough detail that the function can do its job; if we have a function that only ever deals with one type, we look long and hard at the design decisions that made us pass around void * values instead of that type.

For sanity's sake (and, depending on the compiler, performance), please consider also marking arguments you are not writing to as const.

moonshadow
  • 86,889
  • 7
  • 82
  • 122
0

If you know that the pointers that you cast to void * are always to a known set of types (e.g. char, short, int, etc.), you can create a struct type consisting of a union together with a discriminant, and pass that instead, re-casting if necessary.

You can pass it as a pointer to an undefined type, just to be a bit more specific than void *, and in that way some responsibility is taken.

davernator
  • 240
  • 1
  • 4