When is casting between pointer types not undefine

2019-01-06 11:06发布

As a newcomer to C, I'm confused about when casting a pointer is actually OK.

As I understand, you can pretty much cast any pointer type to any other type, and the compiler will let you do it. For example:

int a = 5;
int* intPtr = &a;
char* charPtr = (char*) intPtr; 

However, in general this invokes undefined behavior (though it happens to work on many platforms). This said, there seem to be some exceptions:

  • you can cast to and from void* freely (?)
  • you can cast to and from char* freely (?)

(at least I've seen it in code...).

So which casts between pointer types are not undefined behaviour in C?

Edit:

I tried looking into the C standard (section "6.3.2.3 Pointers", at http://c0x.coding-guidelines.com/6.3.2.3.html ), but didn't really understand it, apart from the bit about void*.

Edit2:

Just for clarification: I'm explicitly only asking about "normal" pointers, i.e. not about function pointers. I realize that the rules for casting function pointers are very restrictive. As I matter of fact, I've already asked about that :-): What happens if I cast a function pointer, changing the number of parameters

5条回答
一纸荒年 Trace。
2楼-- · 2019-01-06 11:41

The authors of the Standard made no attempt to weigh the costs and benefits of supporting conversions among most combinations of pointer types on platforms where such support would be expensive, since:

  1. Most platforms where such conversions would be expensive would likely have been obscure ones the authors of the Standard didn't know about.

  2. People using such platforms would be better placed than the authors of the Standard with the costs and benefits of such support.

If some particular platform uses a different representation for int* and double*, I think the Standard would deliberately allow for the possibility that e.g. round-drip conversion from double* to int* and back to double* would work consistently but conversions from int* to double* and back to int* might fail.

I don't think the authors of the Standard intended that such operations might fail on platforms where such conversions cost nothing. They described the Spirit of C in the charter and rationale documents as including the principle "Don't prevent [or needlessly obstruct] the programmer from doing what needs to be done." Given that principle, there would be no need for the Standard to mandate that implementations process actions in a way that helps programmers accomplish what they need to do in cases where doing so would cost nothing, since implementations that make a bona fide effort to uphold the Spirit of C will behave in such fashion with or without a mandate.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-01-06 11:43

Generally, if as usual nowadays the pointers themselves have the same alignment properties, the problem is not the cast itself, but whether or not you may access the data through the pointer.

Casting any type T* to void* and back is guaranteed for any object type T: this is guaranteed to give you exactly the same pointer back. void* is the catch all object pointer type.

For other casts between object types there is no guarantee, accessing an object through such a pointer may cause all sorts of problems, such as alignments (bus error), trap representations of integers. Different pointer types are not even guaranteed to have the same width, so theoretically you might even loose information.

One cast that should always work, though, is to (unsigned char*). Through such a pointer you may then investigate the individual bytes of your object.

查看更多
ら.Afraid
4楼-- · 2019-01-06 11:45

Oli Charlesworth's excellent answer lists all cases where casting a pointer to a pointer of a different type gives a well-defined result.

In addition, there are four cases where casting a pointer gives implementation-defined results:

  • You can cast a pointer to an sufficiently large (!) integer type. C99 has the optional types intptr_t and uintptr_t for this purpose. The result is implementation-defined. On platforms that address memory as a contiguous stream of bytes ("linear memory model", used by most modern platforms), it usually returns the numeric value of the memory address the pointer points to, thus simply a byte count. However, not all platforms use a linear memory model, which is why this is implementation-defined :-).
  • Conversely, you can cast an integer to a pointer. If the integer has a type large enough for intptr_t or uintptr_t and was created by casting a pointer, casting it back to the same pointer type will give you back that pointer (which however may no longer be valid). Otherwise the result is implementation-defined. Note that actually dereferencing the pointer (as opposed to just reading its value) may still be UB.
  • You can cast a pointer to any object to char*. Then the result points to the lowest addressed byte of the object, and you can read the remaining bytes of the object by incrementing the pointer, up to the object's size. Of course, which values you actually get is again implementation-defined...
  • You can freely cast null pointers, they'll always stay null pointers regardless of pointer type :-).

Source: C99 standard, sections 6.3.2.3 "Pointers", and 7.18.1.4 "Integer types capable of holding object pointers".

As far as I can tell, all other casts of a pointer to a pointer of a different type are undefined behavior. In particular, if you are not casting to char or a sufficiently large integer type, it may always be UB to cast a pointer to a different pointer type - even without dereferencing it.

This is because the types may have different alignment, and there is no general, portable way to make sure different types have compatible alignment (except for some special cases, such as signed/unsigned integer type pairs).

查看更多
相关推荐>>
5楼-- · 2019-01-06 11:48

It's undefined behaviour, when you cast to a type with a different size. For example, casting from a char to an int. A char is 1 byte long. Integers are 4 bytes long (on a 32 bit Linux system). So if you have a pointer to a char and you cast that to a pointer to an int, that will cause undefined behaviour. Hope this helps.

Something like the following below, would cause undefined behaviour:

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

int main() {

    char *str = "my str";
    int *val;

    val = calloc(1, sizeof(int));
    if (val == NULL) {
        exit(-1);
    }
    *val = 1;

    str = (char) val;

    return 0;
}

EDIT: What Oli said about void* pointers is correct, by the way. You can cast between any void pointer and another pointer.

查看更多
啃猪蹄的小仙女
6楼-- · 2019-01-06 11:57

Basically:

  • a T * may be freely converted to a void * and back again (where T * is not a function pointer), and you will get the original pointer.
  • a T * may be freely converted to a U * and back again (where T * and U * are not function pointers), and you will get the original pointer if the alignment requirements are the same. If not, the behaviour is undefined.
  • a function-pointer may be freely converted to any other function-pointer type and back again, and you will get the original pointer.

Note: T * (for non-function-pointers) always satisfies the alignment requirements for char *.

Important: None of these rules says anything about what happens if you convert, say, a T * to a U * and then try to dereference it. That's a whole different area of the standard.

查看更多
登录 后发表回答