Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C standard compliant way to access null pointer address?

In C, deferencing the null pointer is Undefined Behavior, however the null pointer value has a bit representation that in some architectures make it points to a valid address (e.g the address 0).
Let's call this address the null pointer address, for the sake of clarity.

Suppose I want to write a piece of software in C, in an environment with unrestrained access to memory. Suppose further I want to write some data at the null pointer address: how would I achieve that in a standard compliant way?

Example case (IA32e):

#include <stdint.h>  int main() {    uintptr_t zero = 0;     char* p = (char*)zero;     return *p; } 

This code when compiled with gcc with -O3 for IA32e gets transformed into

movzx eax, BYTE PTR [0] ud2 

due to UB (0 is the bit representation of the null pointer).

Since C is close to low level programming, I believe there must be a way to access the null pointer address and avoid UB.


Just to be clear
I'm asking about what the standard has to say about this, NOT how to achieve this in a implementation defined way.
I know the answer for the latter.

like image 661
Margaret Bloom Avatar asked Feb 21 '16 14:02

Margaret Bloom


People also ask

What are the ways to a null pointer that can be used in the C programming language?

Some uses of the null pointer are: a) To initialize a pointer variable when that pointer variable isn't assigned any valid memory address yet. b) To pass a null pointer to a function argument when we don't want to pass any valid memory address. c) To check for null pointer before accessing any pointer variable.

What is the address of a null pointer in C?

Memory address 0 is called the null pointer. Your program is never allowed to look at or store anything into memory address 0, so the null pointer is a way of saying "a pointer to nothing".

What happens when we try to access null pointer in C?

Because a null pointer does not point to a meaningful object, an attempt to dereference (i.e., access the data stored at that memory location) a null pointer usually (but not always) causes a run-time error or immediate program crash. In C, dereferencing a null pointer is undefined behavior.

Can we assign null to pointer in C?

In C programming language a Null pointer is a pointer which is a variable with the value assigned as zero or having an address pointing to nothing. So we use keyword NULL to assign a variable to be a null pointer in C it is predefined macro.


2 Answers

I read (part of) the C99 standard to clear my mind. I found the sections that are of interest for my own question and I'm writing this as a reference.

DISCLAIMER
I'm an absolute beginner, 90% or more of what I have written is wrong, makes no sense, or may break you toaster. I also try to make a rationale out of the standard, often with disastrous and naive results (as stated in the comment).
Don't read.
Consult @Olaf, for a formal and professional answer.

For the following, the term architectural address designed a memory address as seen by the processor (logical, virtual, linear, physical or bus address). In other word the addresses that you would use in assembly.


In section 6.3.2.3. it reads

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

and regarding integer to pointer conversion

An integer may be converted to any pointer type. Except as previously specified [i.e. for the case of null pointer constant], 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.

These imply that the compiler, to be compliant, need only to implement a function int2ptr from integer to pointers that

  1. int2ptr(0) is, by definition, the null pointer.
    Note that int2ptr(0) is not mandated to be 0. It can be any bit representation.
  2. *int2ptr(n != 0) has no constraints.
    Note that this means that int2ptr needs not to be the identity function, nor a function that return valid pointers!

Given the code below

char* p = (char*)241; 

The standard makes absolute no guarantee that the expression *p = 56; will write to the architectural address 241.
And so it gives no direct way to access any other architectural address (including int2ptr(0), the address designed by a null pointer, if valid).

Simply put the standard does not deal with architectural addresses, but with pointers, their comparison, conversions and their operations.

When we write code like char* p = (char*)K we are not telling the compiler to make p point to the architectural address K, we are telling it to make a pointer out of the integer K, or in other term to make p point to the (C abstract) address K.

Null pointer and the (architectural) address 0x0 are not the same (cit.) and so is true for any other pointer made from the integer K and the (architectural) address K.

For some reasons, childhood heritages, I thought that integer literals in C could be used to express architectural addresses, instead I was wrong and that only happen to be (sort of) correct in the compilers I was using.

The answer to my own question is simply: There is no standard way because there are no (architectural) address in the C standard document. This is true for every (architectural) address, not only the int2ptr(0) one1.


Note about return *(volatile char*)0;

The standard says that

If an invalid value [a null pointer value is an invalid value] has been assigned to the pointer, the behavior of the unary * operator is undefined.

and that

Therefore any expression referring to such an [volatile] object shall be evaluated strictly according to the rules of the abstract machine.

The abstract machine says that * is undefined for null pointer values, so that code shouldn't differ from this one

return *(char*)0;

which is also undefined.
Indeed they don't differ, at least with GCC 4.9, both compile to the instructions stated in my question.

The implementation defined way to access the 0 architectural address is, for GCC, the use of the -fno-isolate-erroneous-paths-dereference flag which produces the "expected" assembly code.


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.

Unfortunately it says that the & yields the address of its operand, I believe this is a bit improper, I would say that it yields a pointer to its operand. Consider a variable a that is known to resides at address 0xf1 in a 16 bit address space and consider a compiler that implements int2ptr(n) = 0x8000 | n. &a would yield a pointer whose bit representation is 0x80f1 which is not the address of a.

1Which was special to me because it was the only one, in my implementations, that couldn't be accessed.

like image 164
Margaret Bloom Avatar answered Oct 08 '22 17:10

Margaret Bloom


As OP has correctly concluded in her answer to her own question:

There is no standard way because there are no (architectural) address in the C standard document. This is true for every (architectural) address, not only the int2ptr(0) one.

However, a situation where one would want to access memory directly is likely one where a custom linker script is employed. (I.e. some kind of embedded systems stuff.) So I would say, the standard compliant way of doing what OP asks would be to export a symbol for the (architectural) address in the linker script, and not bother with the exact address in the C code itself.

A variation of that scheme would be to define a symbol at address zero and simply use that to derive any other required address. To do that add something like the following to the SECTIONS portion of the linker script (assuming GNU ld syntax):

_memory = 0; 

And then in your C code:

extern char _memory[]; 

Now it is possible to e.g. create a pointer to the zero address using for example char *p = &_memory[0]; (or simply char *p = _memory;), without ever converting an int to a pointer. Similarly, int addr = ...; char *p_addr = &_memory[addr]; will create a pointer to the address addr without technically casting an int to a pointer.

(This of course avoids the original question, because the linker is independent from the C standard and C compiler, and every linker might have a different syntax for their linker script. Also, the generated code might be less efficient, because the compiler is not aware of the address being accessed. But I think this still adds an interesting perspective to the question, so please forgive the slightly off-topic answer..)

like image 37
CliffordVienna Avatar answered Oct 08 '22 17:10

CliffordVienna