EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: Dadu@ on February 21, 2022, 07:31:29 pm

Title: pointer type casting
Post by: Dadu@ on February 21, 2022, 07:31:29 pm
I am confused with casting in c language,  after reading, I  understood that converting one type to another type is called type casting

I found that when we use void pointer or function pointer, casting is used 

Code: [Select]
#include <stdio.h>
 
void function2(int x)
{
   printf("x = %d\n", x);

}

int main ()
{
    void (*f2)(int) = &function2;

    f2(2);

    return 0;   
}

 

i understand that in this line only address of function is being assigned

Code: [Select]
  void (*f2)(int) = &function2;

I don't understand how one type is converted into another type in code?


Title: Re: pointer type casting
Post by: Nominal Animal on February 21, 2022, 08:00:11 pm
There are no casts in the program.

The expression
    void (*f2)(int) = &function2;
does not contain a cast, although it might look like it at a quick glance.  Instead, it declares a function pointer variable named f2.  The function it points to does not return anything, and takes a single int as a parameter.

Often, we typedef function types to make lives easier.  In this case, we could name the type function_type,
    typedef  void (function_type)(int);
and then we can write the first expression as
    function_type *f2 = &function2;
and because the function name alone is a pointer to that function, as
    function_type *f2 = function2;

Code: [Select]
#include <stdio.h>

typedef  void (function_type)(int);

void function2(int x)
{
   printf("x = %d\n", x);
}

int main(void)
{
    function_type *f2 = function2;
    f2(2);
    return 0;
}
Title: Re: pointer type casting
Post by: DavidAlfa on February 21, 2022, 08:11:41 pm
You're not converting anything actually, that's not casting.

https://www.ibm.com/docs/en/i/7.2?topic=program-casting-pointers (https://www.ibm.com/docs/en/i/7.2?topic=program-casting-pointers)

Casting is when using a pointer to force access as a different data type.

This makes a pointer to a void function with (int) input parameter.
Code: [Select]
void (*f2)(int)
Which is the same type as:
Code: [Select]
void function2(int x)
So this basically copies the function address and tells the compiler to use it in the exact same way.
Code: [Select]
void (*f2)(int) = &function2;

A very simple example of casting:
(System endiannes type could cause different behavior)

Imagine you want to point to the lower byte of a long, so it reads 0x44:
Code: [Select]
uint32_t long = 0x11223344;
This would cause warnings, as the compiler expect same data types by default unless you force (Cast) it.
Code: [Select]
uint8_t * byte = &long;
Casting:
Code: [Select]
uint8_t * byte = (uint8_t *)&long;

Now, *byte would read 0x44 .

Casting can be very useful.
Imagine this buffer, where you stored raw data
Code: [Select]
uint8_t buffer[128];
Depending on the packet ID, it might contain different structures.
So you could cast that array as a defined struct datatype and access it's contents easily.
This would have to be done carefully, taking care of the struct padding, but its a good example to understand how it works.
You determined the content is date type:
Code: [Select]
typedef struct{
  uint8_t year;
  uint8_t month;
  uint8_t day;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
} date_t;
So instead of parsing, copying.... You could do this:
Code: [Select]
date_t * date = (date_t *)buffer;

printf("Year:%u, Month:%u, Day:%u \n", date->year, date->month, date->day);
Title: Re: pointer type casting
Post by: Dadu@ on February 22, 2022, 04:47:14 am
I have written two test code and I hope type casting is being used in first code



Code: [Select]
#include<stdio.h>

int main ()
{
int x = 1;
void *ptr = &x;
printf("%d", *(int*)ptr);
return 0;
}
1

Code: [Select]
#include<stdio.h>

int main ()
{
int x = 1;
void *ptr = &x;
printf("%d", *ptr);
return 0;
}

 error: invalid use of void expression

I am trying to understand what's wrong with print statement in second code ? why first code gives expected output and why second doesn't ?
Title: Re: pointer type casting
Post by: Ian.M on February 22, 2022, 07:20:49 am
When you dereference a pointer, the dereferencing operator expression (e.g. *ptr ) returns a value dependent on the type of the pointer.   For consistency on a void* pointer it would have to return an object of void, (i.e. no value) which *CANNOT* be used anywhere that an expression having a value is needed.  In fact the C99 standard says:
Quote
6.3.2.2 void
1 The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression. If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

Therefore at the point of compiling the evaluation of the printf() second argument the compiler has no way of knowing *what* sort of value your void pointer is pointing at, not even if it is a number, so cant use the Usual Arithmetic Conversions to convert it to whatever type printf() expects, and (6.3.2.2) forbids it from attempting to do so.  Also as printf() is a variadic function with mutable argument types after the first, and the compiler cant 'sniff' inside the format string, it doesn't even know what type it *should* be converting it to.

Edit: Corrected first line as there is more than one pointer dereferencing operator.  e.g. apart from the ordinary *ptr you will encounter structptr->structmember as soon as you start learning about linked lists.
Title: Re: pointer type casting
Post by: Nominal Animal on February 23, 2022, 01:13:27 am
Ian.M provided a very detailed answer.  To continue from that (and apologies for repeating some of what Ian.M wrote):

The difference between the working expression,
   printf("%d", *(int*)ptr);
and the non-working expression,
   printf("%d", *ptr);
is the cast of the void pointer to a pointer to an int, before being dereferenced.

Remember, when applied to a pointer type, * is the dereferencing operator: it "reaches through" the pointer, and grabs the value pointed by the pointer.  The type of that value is defined by the pointer type.

If we have
    int   *ptr1;
    float *ptr2;
and we properly set them to point somewhere valid, then *ptr1 is the integer that ptr1 points to, and *ptr2 is the float that ptr2 points to.



At this point, I'd like to repeat something I basically always tell those who are learning about pointers in C.

We have three types of pointers: void pointers, object pointers, and function pointers.

Function pointers point to functions, and can be called like functions.

Object pointers point to data of some type.

Void pointers are those that do not specify a type at all.  POSIX C states that void pointers can be cast to and from both object and function pointers, but standard C does not allow conversion between object and function pointers and does not allow casting a void pointer into a function pointer.  This leads to some unfortunate language-lawyerism wrt. dlsym() (https://man7.org/linux/man-pages/man3/dlsym.3.html) on POSIXy systems (Linux, Android, Mac OS, BSDs) when portability is discussed.  The reason for this is some historical ABIs had certain features that made the distinction useful.  On AVR and ARM microcontrollers, object and function pointers are interchangeable.  Those with Harvard architectures (separate address spaces for Flash and RAM) can use compiler-provided address space attributes; for example GCC and Clang have excellent support for these now (in 2022), although not all existing code (especially Arduino) use these very effectively.

When reading an object or void pointer type or type specification, split it at each *.  Start reading from right (starting at the variable name, if any), each split part at a time from right to left, replacing the * with "is a pointer to".  This does not work for function pointers because their syntax is more complex, and you can argue how one should read qualifiers like const or volatile, but it does give a pretty good intuitive understanding.

For example, char *const *ptr could be described in English as "ptr is a pointer to const pointer to char".  This means that it is an indirect pointer, it points to a pointer to char.  The variable ptr itself may be modified, and the char or char array the indirect pointer points to may be modified as well, but the type includes a promise that we do not try to modify the pointer that points to the first char, i.e. we promise not to try and modify *ptr.

Finally, array notation is mostly just a convenient form of pointer notation.  If we have char *s that points to a string –– a string being an array of char terminated with a nul char '\0' ––, then both *s and s[0] refer to the first character in that string.  Similarly, given some integral type variable i (usually size_t, ssize_t, or int, although int may be limited to a smaller range), si] and *(s + i) both refer to the same, i+1'th character of the string (since i = 0 is the first one).

One quirk of C array notation, related to this, should only be used in Obfuscated C contests.  It is that i[a] is the same as a[i], if a is an array or pointer expression, and i is an expression having some integral type.  Thus, for example (x+w*y)[a] is the same as a[x+w*y], if x, y, and w have some integral type, and a is an array or a pointer or a pointer expression.  This only leads to confusion, and should never be used.  Nevertheless, knowing about this is useful, because if one accidentally writes such an expression, the compiler will not complain about it (because it is valid C), so it is important for us human programmers to detect these instead.  (I would even go as far as rejecting code that relies on such, because if code is intended to be confusing, I definitely don't want anything to do with it.)
Title: Re: pointer type casting
Post by: newbrain on February 24, 2022, 08:20:40 am
When reading an object or void pointer type or type specification, split it at each *.  Start reading from right (starting at the variable name, if any), each split part at a time from right to left, replacing the * with "is a pointer to".  This does not work for function pointers because their syntax is more complex, and you can argue how one should read qualifiers like const or volatile, but it does give a pretty good intuitive understanding.
To read complex (or simple) declarations, using the spiral mnemonic trick (http://www.c-faq.com/decl/spiral.anderson.html) is still probably the best way.

Unless one is really lazy, and just goes to https://cdecl.org (https://cdecl.org)
Title: Re: pointer type casting
Post by: DiTBho on February 24, 2022, 11:26:22 am
Type-Casting, and in particular Ponter-Type-Casting, should be used as little as possible.
It makes tools and people confused, and it also violates QA rules of several certification checkers.

The more Type-Casting you do in your code, the more effort you will have to put in to get your tools and customers and colleagues to understand what your code is supposed to do, and the code itself to pass QA checkers.
Title: Re: pointer type casting
Post by: DavidAlfa on February 24, 2022, 02:15:53 pm
It depends how to you use it.
For example, casting a buffer transferred by another system might be very risky due different alignment/padding/endiannes.

Correct me if I'm wrong, but within the same system, and taking proper care (Easier to say than done),  it should be safe.
Ex. Storing/reading a struct of bits/configs into/from a filesystem using read/write (uint8_t *) custom_struct, sizeof(custom_struct).

In these operations you're casting between (uint8_t*) and (custom_struct *) types.

Btw, I opened a project from 3-4 years ago, I felt happy after seeing how much I had improved since then.
Spent some time optimizing it, looks much much better now.
Yet I'm probably only capable of 15% of what any of you can do haha
Title: Re: pointer type casting
Post by: eugene on February 24, 2022, 03:26:27 pm
It depends how to you use it.
For example, casting a buffer transferred by another system might be very risky due different alignment/padding/endiannes.

Correct me if I'm wrong, but within the same system, and taking proper care (Easier to say than done),  it should be safe.
Ex. Storing/reading a struct of bits/configs into/from a filesystem using read/write (uint8_t *) custom_struct, sizeof(custom_struct).

In these operations you're casting between (uint8_t*) and (custom_struct *) types.

Btw, I opened a project from 3-4 years ago, I felt happy after seeing how much I had improved since then.
Spent some time optimizing it, looks much much better now.
Yet I'm probably only capable of 15% of what any of you can do haha

In most (all?) cases that I've encountered, it was possible to use unions instead of casting pointers. This is a conversation that is out of my league, so I don't know if unions are inherently safer/preferable, but I tend to find them easier to understand without error.
Title: Re: pointer type casting
Post by: DiTBho on February 24, 2022, 04:59:56 pm
if unions are inherently safer/preferable, but I tend to find them easier to understand without error.

For me: they are better  :-+
Title: Re: pointer type casting
Post by: Nominal Animal on February 24, 2022, 05:01:10 pm
Pointer casting is typically done between void * and/or unsigned char *, and object pointers.

void *, or void pointers, are the "generic" pointer type.  For example, malloc()/calloc()/realloc() return one.  You do not need an explicit cast from void pointer to an object pointer in C: it is perfectly normal to do e.g. char *ptr = malloc(421);.  Note, however, that you cannot do pointer arithmetic with void pointers, because they do not point to any specific type.

unsigned char * is used with buffers.  C99 6.2.6.1p4 explicitly allows copying the object representation of any type to an unsigned char array using for example memcpy().  If you ever need to do pointer arithmetic to find the address of some item in a buffer, you use unsigned char * pointers (instead of say void *.)

Typical C code only does pointer casts between the above two, or between an object pointer and one of the above pointer types.  Casting say float * to say int * is always suspicious.

To convert a pointer to an integral type, always use uintptr_t (unsigned) or intptr_t (signed), NEVER int, because int may have insufficient range.  Similarly, if you want to describe the length of an array in memory, you should use size_t instead of int.

Oh, and sizeof (char) == sizeof (signed char) == sizeof (unsigned char) == 1, says the C standard.  If you want to know how many bits you have in an unsigned int, include <limits.h> and use (CHAR_BIT * sizeof (unsigned int)).  See e.g. C99 6.2.6.1p4.



Type punning, i.e. reinterpreting the object representation of some type as another type, is most portable if done using an union (see e.g. C99 6.5.2.3 footnote 82).

That is,
Code: [Select]
float  u32_to_float(uint32_t  val)
{
    union {
        uint32_t  u;
        float     f;
    } temp = { .u = val };
    return temp.f;
}

uint32_t  float_to_u32(float  val)
{
    union {
        uint32_t  u;
        float     f;
    } temp = { .f = val };
    return temp.u;
}
Title: Re: pointer type casting
Post by: nctnico on February 24, 2022, 07:33:19 pm
Type-Casting, and in particular Ponter-Type-Casting, should be used as little as possible.
It makes tools and people confused, and it also violates QA rules of several certification checkers.
IMHO type casting shouldn't be used at all. If it is needed, it shows the basic structure / architecture of the program has not been thought through well enough.
Title: Re: pointer type casting
Post by: Nominal Animal on February 25, 2022, 06:00:02 am
IMHO type casting shouldn't be used at all. If it is needed, it shows the basic structure / architecture of the program has not been thought through well enough.
No, type casting, either implicit or explicit, between the pointer types I specified, is often required/needed/useful.  An actual example:
Code: [Select]
#include <unistd.h>
#include <errno.h>

/* Async-signal-safe write() variant.  Keeps errno unchanged.
   Retries if interrupted by signal delivery.  Assumes the descriptor is blocking.
   Returns 0 if entire buffer written, errno error code otherwise.
*/
int write_all(int descriptor, const void *buffer, size_t length)
{
    const char *ptr = buffer;
    const char *const end = (const char *)buffer + length;
    ssize_t  n;
    int  saved_errno;

    if (descriptor == -1)
        return EBADF;

    if (length < 1)
        return 0;

    saved_errno = errno;

    while (ptr < end) {
        n = write(descriptor, ptr, (size_t)(end - ptr));
        if (n == -1) {
            const int  err = errno;
            if (err == EINTR)
                continue;   
            errno = saved_errno;
            return err;
        } else
        if (n < 1) {
            errno = saved_errno;
            return EIO;
        }
        ptr += n;
    }

    errno = saved_errno;
    return 0;
}
The assignment const char *ptr = buffer; contains an implicit pointer type cast.  In C, you can assign a void pointer to any object pointer, or vice versa, without an explicit cast.  This assignment is therefore exactly the same as const char *ptr = (const char *)buffer;.

The assignment const char *const end = (const char *)buffer + length; requires an explicit cast, because we want a pointer to the byte just past the buffer, and pointer arithmetic cannot be done on void pointers.  Technically, we could have used const char *const end = ptr + length;, in which case we would have relied on only the implicit pointer type cast.

The rest of the function is just a standard retry loop, which understands both EINTR (interrupted) and short writes.  The 'blocking' means non-nonblocking, i.e. that the descriptor is not in nonblocking mode.

Note that one can use the void pointer implicit cast to avoid having to do any explicit casts, simply by casting to an intermediate void pointer type.  That is why I do not consider implicit pointer type casts an exception: it is the initial (source) and final (target) types that matter, not the chain of type casts, implicit or explicit, used.

(With the example function, the conversion is from whatever data type is to be written to the file descriptor or socket, to void pointer as the function argument, to char pointer so that pointer arithmetic can be used.  If buffer was a char pointer, then every caller would need to cast the pointer to the data they want to write explicitly to a char pointer.)
Title: Re: pointer type casting
Post by: Siwastaja on February 25, 2022, 07:02:33 am
Type-Casting, and in particular Ponter-Type-Casting, should be used as little as possible.
It makes tools and people confused, and it also violates QA rules of several certification checkers.
IMHO type casting shouldn't be used at all. If it is needed, it shows the basic structure / architecture of the program has not been thought through well enough.

Complete BS. You can't write real-life C without type casting.

Typical examples are malloc, buffers, and arithmetic operations where the result does not fit into the type of input operands (like multiplications). I'm 100% sure you are using implicit and explicit casting in these use cases all the time, yet you say differently.

(Obviously, with arithmetic, you can avoid the explicit casting by creating enough temporary variables, but this isn't going to make code look or work any better. Classical "obfuscation to avoid X".)

Pointer type casting is needed when writing high-quality, generic C code; examples being handler functions for buffers. Avoiding pointer type casting means writing beginner spaghetti code which results in longer code more prone to bugs.

I'm surprised that you are this confused. I thought you would know better.
Title: Re: pointer type casting
Post by: TheCalligrapher on March 02, 2022, 05:05:56 pm
I am confused with casting in c language,  after reading, I  understood that converting one type to another type is called type casting

Um, no... The term "casting" is normally used when you force the conversion manually, explicitly, by using a cast operator. The language can also convert types implicitly - without a cast.

I found that when we use void pointer or function pointer, casting is used 

Code: [Select]
#include <stdio.h>
 
void function2(int x)
{
   printf("x = %d\n", x);

}

int main ()
{
    void (*f2)(int) = &function2;

    f2(2);

    return 0;   
}
 

I'm not sure what you mean. There are no type conversions in this code at all. There are no casts.

i understand that in this line only address of function is being assigned

Code: [Select]
  void (*f2)(int) = &function2;

I don't understand how one type is converted into another type in code?

Not assigned, but initialized. And again, there's no casting in this code and no type conversions. What exactly are you asking about?
Title: Re: pointer type casting
Post by: SiliconWizard on March 02, 2022, 06:28:02 pm
Yes, unless it's explicit, I think the std talks about implicit conversion rather than implicit casting.

As to pointers, the example above indeed does not require any conversion. Also, the '&' operator before the function name is useless here. It's allowed, but is unnecessary and (at least to me) hinders readability (but that may be personal preference.)

On a related note, going to and from void * (something you sometimes have to do to write "generic" enough code) does not require any explicit cast, and using a cast in this context is often considered bad practice in C. It might be done by some to make source code compatible with C++, which is a whole other can of worms. I personally advise against it.
Title: Re: pointer type casting
Post by: DiTBho on March 02, 2022, 08:30:07 pm
Complete BS. You can't write real-life C without type casting.

The firmware I wrote for industrial embroidery machine is about 7K lines of C. It's polymorphic, b*tree based with a kind of virtual-memory custom engine I wrote, and there are only 13 points where you can observe pointer/data type-casting  :D

13 / 7000 = 0.18% ----> that's what I mean with "in some few cases"