14

I am trying to find out if the following forward declaration is valid in ANSI-C:

First file:

extern void * fptr;   // opaque forward declaration.
int main (void) {
  fptr = NULL;        // set the function pointer to NULL
}

Second file:

typedef int (*fptr_t)(int);
fptr_t fptr;         // real declaration of the function pointer

To me, this should be invalid since fptr if declared with two different types, but neither gcc nor clang gives any warning.

I would be more specifically interested in precise points of the C11 standard that allow to conclude why it is valid (or invalid).


EDIT: in the C11 standard,6.2.7:2 says:

All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

But I cannot find how to decide if void* is compatible with fptr_t.

Anne
  • 1,270
  • 6
  • 15
  • 6
    You aren't going to get compile-time errors if the two declarations never appear together in the same compilation unit. When the object files are linked, if the symbol references can be resolved you won't get an error, even if the types are incompatible. It's no different from declaring a variable to be an `int` in one file and a `float` in another. – Tom Karzes Apr 21 '16 at 07:03
  • I understand that the compiler can't see the problem. But if you are declaring a variable to be an int in one file and a float in another, you'll get into troubles, won't you? What about this case? – Anne Apr 21 '16 at 07:08
  • As far as I understand the standard, it is ok if the types are "compatible". But are they or not in this case? – Anne Apr 21 '16 at 07:12
  • You are correct, but in this case I believe it's implementation-specific, and it also depends on how you use variables. It will only do what you want if assignment between the pointer types does not result in any change in representation. But it isn't something you should rely on. – Tom Karzes Apr 21 '16 at 07:12
  • @LPs No, the compiler will give an error if it sees the two declarations in the same compilation unit. The *linker* is what would or would not be in a position to warn. You are confusing the linker with the compiler. They are very different. – Tom Karzes Apr 21 '16 at 07:15
  • @TomKarzes No one here said: _same compilation unit_. If you declare an `extern double var` in a different c file than `int var` is declared, no warning will be shown. Even if you move the extern to a header file that is not included into the c file where `int var` is declared. Am I wrong? BTW I know clearly the difference between linker and compiler...;) – LPs Apr 21 '16 at 07:27
  • @LPs You said: "I think that if variables match their sizes compiler will not warn you." That implies that the compiler is what's seeing the two declarations. That implies they're in the same compilation unit. You clearly meant "linker", not "compiler". Hence my attempt to explain the difference. – Tom Karzes Apr 21 '16 at 07:31
  • @TomKarzes Yes, ok. My bad english made a new victim. I understood. Thx. – LPs Apr 21 '16 at 07:33
  • @Anne Follow this simple rule: two types are compatible if they are of the *same* type. – 2501 Apr 21 '16 at 08:10

4 Answers4

7

C99:

6.2.7 Compatible type and composite type

clause 2:

All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

6.7.5.1 Pointer declarators

clause 2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

Without further digging in the standard, it's easy to see that void and a function are not compatible types.

I'm willing to bet this doesn't change in C11. C has implicitly supported distinct code and data spaces and different sizes and representations of code and data pointers for a long time and it would be strange to remove this feature and confine the language to a smaller subset of machines to be available for. So, downvote with caution. Better with proof.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • It is the same in C11. You might want to add that two types are compatible if their types are the same (with some exceptions, but not in this case). – 2501 Apr 21 '16 at 08:03
  • I agree with you on the result (pointer to void and pointer to function are not compatible) but not on the reason: `void` and `char` are not compatible types, but `void *` and `char *` are. – Serge Ballesta Apr 21 '16 at 08:18
  • 1
    @SergeBallesta AFAIK, `void*` was purposefully introduced as compatible to `char*` and that's an exception. However, there hasn't been such an exception ever made for pointers to functions and pointers to data (or void). – Alexey Frunze Apr 21 '16 at 08:23
  • It is worth noting that a considerable number of implementations do guarantee that `void *` can be safely converted to and from a function pointer. POSIX makes this guarantee, for instance. – Nate Eldredge Apr 21 '16 at 15:03
6

No, it is not valid, because essentially you are storing a regular pointer (NULL, void*) into a memory location which is actually a function pointer. You're just hiding that from the compiler, and the linker doesn't care, but at the end of the day you have undefined behavior, because the two pointer types are not necessarily compatible. Of course it may work on many systems, but perhaps not all.

For more on function pointers vs void pointers, see here: can void* be used to store function pointers? - while this is a slightly different case than what you're presenting, the answers are still relevant.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

It should work but it is invalid. In first file, you declare that the identifier fptr will be defined in another compilation unit and that it will be a void *. In second file, you define the identifier but it is now a pointer to function. The compiled files generally do not keep the type of the objects (only the address) so:

  • the compiler has no idea that the other source declares a different type and cannot emit any warning
  • the linker is not required to control that so no warning is required by standard, an common implementation do not control types so no warning either

In common implementations, all pointers have same representation, and conversion of a pointer to function to a pointer to void does not change the representation, so the aliasing will give expected results.

But it is still undefined behaviour(*) per standard because 6.2.5 Types § 27 declares (emphasize mine):

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.39) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

(*) as it is not tested by common implementation, there is little risk that the UB will be detected and optimized away by current versions of compilers. But I would never do that in production code...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • If an architecture uses a virtual address space for each logical space (code, data, etc), then it could be possible to have a pointer to function equals to say A, and a pointer to object equals to A, both referring to different address spaces. (Harvard Architecture). – Jean-Baptiste Yunès Apr 21 '16 at 08:11
  • @Jean-BaptisteYunès The pointers will not compare equal if they don't point to the same object. – 2501 Apr 21 '16 at 08:19
  • No it can! The difference can be made in the architecture. For example JUMP A may jump to a different location than LOAD A will load. Two different instruction may use different address spaces to interpret the same *value*. So your two pointers may looks like containing the same value, but this value may be interpreted differently... – Jean-Baptiste Yunès Apr 21 '16 at 08:23
  • @Jean-BaptisteYunès You can only meaningfully compare pointers to different objects using the `==` operator, that is what my previous comment refers to. You were talking (I'm assuming) about the values of the actual bits of the pointer, which can be the same for two pointers to different objects, but that is out of scope of C, i.e. C doesn't define what the bit values are. – 2501 Apr 21 '16 at 08:26
  • I dislike the first sentence: *"It is both correct and invalid."* It's only invalid. Sure, it might work on some systems, but that doesn't make it correct. – user694733 Apr 21 '16 at 08:56
  • @user694733: you are right, but I failed to find a correct phrasing (english is not my first language). Hope it's more clear now – Serge Ballesta Apr 21 '16 at 09:01
1

void * is an object type pointer which is different than function type pointer. They are not compatible types.

But:

Portabilitiy issues J.5.7. Function pointer casts

  1. A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).

  2. A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).

Then why not fully hide the pointer in your module, and externalize functions to manipulate it? This will avoid the aliasing problem.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • It is not in some code of mine, so I cannot change it. I can only report it to the developers, but I want to be sure that it is a problem. – Anne Apr 21 '16 at 08:14
  • Well, that is a problem from reference books, but there is so many platforms on which is will work correctly... Life is sometimes a matter of compromises. If that code is not embedded in some airplane control but only in my candy crush game... – Jean-Baptiste Yunès Apr 21 '16 at 08:18