15

This question is meant to be a FAQ entry for all initialization/assignment between integer and pointer issues.


I want to do write code where a pointer is set to a specific memory address, for example 0x12345678. But when compiling this code with the gcc compiler, I get "initialization makes pointer from integer without a cast" warnings/errors:

int* p = 0x12345678;

Similarly, this code gives "initialization makes integer from pointer without a cast":

int* p = ...;
int i =  p;

If I do the same outside the line of variable declaration, the message is the same but says "assignment" instead of "initialization":

p = 0x12345678; // "assignment makes pointer from integer without a cast"
i = p;          // "assignment makes integer from pointer without a cast"

Tests with other popular compilers also give error/warning messages:

  • clang says "incompatible integer to pointer conversion"
  • icc says "a value of type int cannot be used to initialize an entity of type int*"
  • MSVC (cl) says "initializing int* differs in levels of indirection from int".

Question: Are the above examples valid C?


And a follow-up question:

This does not give any warnings/errors:

int* p = 0;

Why not?

Lundin
  • 195,001
  • 40
  • 254
  • 396

1 Answers1

16

No, it is not valid C and has never been valid C. These examples are so-called constraint violations of the standard.

The standard does not allow you to initialize/assign a pointer to an integer, nor an integer to a pointer. You need to manually force a type conversion with a cast:

int* p = (int*) 0x1234;

int i = (int)p;

If you don't use the cast, the code is not valid C and your compiler is not allowed to let the code pass without displaying a message. The cast operator states: C17 6.5.4/3:

Constraints
/--/
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.

6.5.16.1 being the rules of simple assignment that allow certain implicit conversions of pointers, see C17 6.5.16.1 §1:

6.5.16.1 Simple assignment

Constraints

One of the following shall hold:

  • the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;
  • the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;
  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or
  • the left operand has type atomic, qualified, or unqualified _Bool, and the right is a pointer.

In case of int* p = 0x12345678;, the left operand is a pointer and the right is an arithmetic type.
In case of int i = p;, the left operand is an arithmetic type and the right is a pointer.
Neither of these fit in with any of the constraints cited above.

As for why int* p = 0; works, it is a special case. The left operand is a pointer and the right is a null pointer constant. More info about the difference between null pointers, null pointer constants and the NULL macro.


Some things of note:

  • If you assign a raw address to a pointer, the pointer likely needs to be volatile qualified, given that it points at something like a hardware register or an EEPROM/Flash memory location, that can change its contents in run-time.

  • Converting a pointer to an integer is by no means guaranteed to work even with the cast. The standard (C17 6.3.2.3 §5 and §6 says):

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation. 68)

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

Informative foot note:

68) The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.

In addition, the address from a pointer might be larger than what will fit inside an int, as is the case for most 64 bit systems. Therefore it is better to use the uintptr_t from <stdint.h>

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • You could add sth about the footnote 67) of C11: *The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.* I.e. a quality implementation wouldn't just pull the numbers from hat ;) – Antti Haapala -- Слава Україні Sep 05 '18 at 13:58
  • 1
    @AnttiHaapala Added the foot note. And yeah pre-ANSI tended to treat all types as `int`. Regardless, it has never been standardized. More worrying is that some semi-modern embedded systems compilers let this kind of code through without any diagnostic messages. IIRC, some older PC compilers like Borland did this too. – Lundin Sep 05 '18 at 14:11
  • So `p = 0x10000000;` would be OK (not a constrain violation) iff `0x10000000` happened to be another implementation specific _null pointer constant_? – chux - Reinstate Monica Sep 05 '18 at 14:16
  • *"An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant."*; you could add that, from http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3 – Antti Haapala -- Слава Україні Sep 05 '18 at 15:18
  • @chux A _null pointer_ may have the binary representation 0x1234.... whatever, but a _null pointer constant_ is either `0` or `(void*)0`. See the posted link in the answer. – Lundin Sep 05 '18 at 15:21
  • The spec allows `0u`, `0L`, `(void*) 0LL` as a _null pointer constant_, not only `0` as [commented](https://stackoverflow.com/questions/52186834/pointer-from-integer-integer-from-pointer-without-a-cast-issues/52186835?noredirect=1#comment91326855_52186835). Also the spec does not clearly disallow other forms of a _null pointer constant_ - the crux of the question on the [comment](https://stackoverflow.com/questions/52186834/pointer-from-integer-integer-from-pointer-without-a-cast-issues/52186835?noredirect=1#comment91324362_52186835) – chux - Reinstate Monica Sep 05 '18 at 15:27
  • The standard calls ICE with value 0 or such cast to `(void*)` as a null pointer constant. How do you read that other values that are not `== 0` are permissible? Then the entire standard is is void because you can add anything after a list that contains `or`?! – Antti Haapala -- Слава Україні Sep 05 '18 at 15:29
  • I believe the implementation-defined value of `NULL` in the standard refers to a compiler either using `0` or `(void*)0` as the NULL macro. – Lundin Sep 05 '18 at 15:31
  • 1
    Well it can use `0LL` for sure, or `0ULL` or `'\0'` or `(5-5)`. But not `42`. As `42` is not what the standard calls a null pointer constant. – Antti Haapala -- Слава Україні Sep 05 '18 at 15:32
  • "How do you read that other values that are not == 0 are permissible?" --> Had the spec been: "a _null pointer constant_ is an integer constant expression with the value 0, or such an expression cast to type void *" (A is defined only from B or b), I would readily agree with the limited possibilities you suggest. Yet the spec is the other way around (B or b leads to A). As I read that, other forms of _null pointer constant_ are possible, even if not common in practice (C may lead to A). Thanks for your insights. – chux - Reinstate Monica Sep 05 '18 at 15:53
  • I am not saying you are correct or incorrect as much as wondering about the possible interpretations of the C spec. Your assertion is reasonable and certainly true in practice - which the later point tends to win the day when divining ambiguity. – chux - Reinstate Monica Sep 05 '18 at 15:56
  • @chux From the C99 rationale 7.17: "NULL can be defined as any null pointer constant. Thus existing code can retain definitions of NULL as 0 or 0L, but an implementation may also choose to define it as (void*)0. This latter form of definition is convenient on architectures where sizeof(void*) does not equal the size of any integer type." – Lundin Sep 06 '18 at 06:29