Thread: Understand typecasting of pointer

  1. #1
    Registered User
    Join Date
    Feb 2019
    Posts
    97

    Understand typecasting of pointer

    Hi guys,

    Can you please help understand how this pointer typecast of data_in array works?

    Function prototype:

    Code:
    nrf_ringbuf_cpy_put(nrf_ringbuf_t const * p_ringbuf, uint8_t const* p_data, size_t * p_length);
    Code:
    int16_t data_in[] = {1050, 1051, 1052};
    size_t len_in = sizeof(data_in);
    
    nrf_ringbuf_cpy_put(&m_ringbuf, (int8_t *)data_in, &len_in);
    data_in[] is type of int16_t while the function prototype receives as a parameter uint8_t const* p_data. So when calling nrf_ringbuf_cpy_put it typecasts data_in from int16_t to int8_t*

    However I do not understand why that works... I mean if you typecast from int16_t to int8_t* you won't lose one byte of information?

    Could you provide an example to understand how that works?

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    By passing a pointer to uint8_t, you can access all the bytes. Note also that sizeof gives the size in bytes (technically in chars, but chars are usually bytes).
    Code:
    #include <stdio.h>
    #include <stdint.h>
     
    void f(const uint8_t *bytes, size_t size) {
        while (size--) printf("%02x ", (unsigned int)*bytes++);
        printf("\n");
    }
     
    int main() {
        int16_t n[] = { 0x1234, 0x123, 0x12, 0x1 };
        f((uint8_t*)&n, sizeof n);
        return 0;
    }
     
    // Prints: 34 12 23 01 12 00 01 00
    The bytes of the int16_t's are printed in reverse order because my computer (and probably yours, too) is "little-endian".
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    Apr 2021
    Posts
    138
    Remember that pointers in C have type information, but have no limits.

    That is, if I give you a pointer value, such as p = 0x12345678, and say "this is a pointer to struct Foo," you can access a struct Foo at that address. But you can also access a struct Foo at address p + 1, and at address p + 100, and at address p + 100000.

    However, because C attaches type information to pointers, the computation of exactly where address p + 100 is located changes. If you have int16_t * p, then address p + 100 uses the size of the target type to compute the offset! The result is 0x12345678 + 100 * sizeof (*p).

    What does this mean for you?

    First, it means that with a small number of exceptions, a pointer to any one thing can also be a pointer to any other thing. A pointer to int16_t can also be a pointer to int8_t or a pointer to int64_t. The numeric value of the pointer will not change, only the expectations laid on the pointer by the C compiler will change. (That is, the computation of offsets when doing pointer arithmetic.)

    Next, it means that you will see this a lot. A lot!!! This is how malloc() and memcpy() (and many more) from the standard C library work. If you want to allocate a struct Foo, how do you do it? You call malloc(sizeof (struct Foo)) and malloc returns a pointer to some bytes which you store into a struct Foo * p. If you want to copy your struct Foo object from one location to another, how do you do it? You call memcpy(q, p, sizeof (*p)) and pass your struct Foo pointer to a function that expects a pointer to bytes. It copies bytes into your destination pointer.

    How does this work? C is a low-level language. The representation of all these fancy-pants objects is a bunch of bytes all right next to each other. Copying bytes, allocating bytes, is the atomic breakdown of copying or allocating objects. The reason that ANSI-C added the void * pointer type was specifically to remove the requirement to cast the return value of malloc, and the parameter values of bcopy/bzero/etc. (Which were standardized to memcpy, memmove, memset, etc.)

    In your code, you have

    Code:
        int16_t data_in[] = {1050, 1051, 1052};
    If I asked you to describe that, you might say "data_in is an array of 3 sixteen-bit words".

    But when you evaluate the expression sizeof (data_in) you get 6. Why is that? Because C works with sizes in bytes. If you want to know "how many" objects are in an array, you actually have to write your own macro.

    Code:
        #define ARRAY_LEN(a) (sizeof (a) / sizeof (*(a)))

    Why cast, then?

    The requirement to cast is to remove a source of error -- developers passing the wrong value as an argument. If you call fprintf() you pass a file pointer, a format string, and possibly some other arguments:

    Code:
        fprintf(stderr, "You made a mistake!\n");
    What happens when you do the same thing with fputs()?

    Code:
        fputs(stderr, "You made a mistake!\n");
    It turns out, a compilation error! Why? Because the arguments to the printf/scanf functions are backwards relative to most of the other file operations: the printf/scanf functions take their file pointer first, because the arguments are varargs, while the other functions take their file pointer last! Whoops!

    ANSI C added prototypes specifically so that the C compiler could type check the arguments being passed to functions. Prior to this, there was no type compatibility requirement, and most compilers didn't bother checking (they assumed you would run lint if you wanted that sort of thing).

    The converse of this is that if you want to pass an argument of the "wrong" type, the compiler will complain and/or raise an error. So how do you do that? You cast the type of the expression to be the type that the parameter expects to receive. This tells the compiler, "Shh! I know it's supposed to be a type T. I'm passing this other type, but it will be okay."

    So when you have a int16_t * pointer, and you want to pass it to your function that expects an int8_t * as input, you cast the type: (int8_t*)p. And that silences the compiler error because the compiler trusts you to know what you are doing. If you don't know what you are doing, something is going to break. And that will be your fault because you don't know what you are doing.

    What are those exceptions you mentioned?

    There are some exceptions to the "a pointer to any thing can be a pointer to any other thing" statement.

    First, there is alignment. Some types, on some CPU architectures, have alignment requirements. Sometimes, like on the x86/x64, this means there is a (slight) performance hit if you access something in a non-aligned fashion. Other times, the access may actually fail (generate a signal or a trap). Generally, this means that it's okay to cast from, for example, int64_t * down to int8_t *, but not the other way without checking!

    Next, there is pointer size. The C standard differentiates three kinds of pointers: pointers to code, pointers to data, and void * / char * pointers. In general, standard C doesn't want you to try to interchange pointers to code and data. However, it requires that void * be able to store any pointer, and void * and char * be interchangeable. Bottom line: don't mix code and data pointers.

    Finally, there is pointer size (again). Some embedded environments will use "bank switching" to extend the address space by replacing parts of memory with different "banks". This effectively multiplies the available ROM (usually) by 2x to 16x, which can be helpful in a 16-bit environment. The cost is storing a "bank address" as well as a pointer value. Other CPUs, including the x86 family, have "segment" registers that can be combined (in "real mode" when there is no active MMU) with general purpose registers to produce a wider (20-bit) address range. This leads to different "memory models", and the idea of "near pointers" (16-bit) and "far pointers" (32-bit). These are handled by adding extra keywords to the language in the particular compiler. The take-away from this is that, once again, pointers may not all be the same size.

  4. #4
    Registered User
    Join Date
    Feb 2019
    Posts
    97
    Thank you John!!

    Quote Originally Posted by john.c View Post
    f((uint8_t*)&n, sizeof n);
    Why you passing the array by reference?
    Code:
    f((uint8_t*)n, sizeof n);
    That's not the same?

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Nikosant03
    Why you passing the array by reference?
    I'd say it's a matter of style: as n is an array, the address resulting from implicitly converting n to a pointer and from &n is the same. Since you're coercing the type anyway, there is thus no difference. But consider calling f for an int:
    Code:
    int x = 123;
    f((uint8_t*)&x, sizeof x);
    Now, taking the address of x is necessary since you're using f to treat x as an array of bytes accessed via a pointer to the start of the array, and there is no implicit conversion from int to a pointer. Consequently, you might choose to always take the address of the object for consistency, thus the use of (uint8_t*)&n instead of (uint8_t*)n.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  6. #6
    Registered User
    Join Date
    Feb 2019
    Posts
    97
    I see, thanks laserlight!!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Of pointer typecasting and other matters...
    By Dren in forum C Programming
    Replies: 4
    Last Post: 08-17-2018, 08:02 PM
  2. typecasting a char pointer
    By tubby123 in forum C Programming
    Replies: 5
    Last Post: 07-12-2011, 02:02 AM
  3. Pointer and typecasting question
    By A34Chris in forum C Programming
    Replies: 4
    Last Post: 04-28-2011, 08:14 AM
  4. Pointer Arithmetic and Typecasting
    By pobri19 in forum C Programming
    Replies: 2
    Last Post: 03-19-2009, 11:06 PM
  5. Typecasting a void* to a function pointer
    By Procyon in forum C++ Programming
    Replies: 2
    Last Post: 01-14-2004, 05:43 PM

Tags for this Thread