Nikos C.
Nikos C.

Reputation: 51832

Casting a struct to a pointer type that points to its first element

While looking at:

Can a C compiler add padding before the first element in a structure?

I came up with the following code:
(Ignore the fact that memory isn't freed in this example.)

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

typedef struct {
    char *cstr;
    size_t len;
} str_t;

void setStr(str_t* dest, const char* src)
{
    size_t len = strlen(src);
    dest->cstr = malloc(len + 1);
    dest->len = len;
    memcpy(dest->cstr, src, len + 1);
}

int main(void)
{
    str_t str;
    setStr(&str, "woot!");
    printf("%s\n", str);
    return 0;
}

Amazingly, this actually works. This call:

printf("%s\n", str);

seems to be equivalent to this one:

printf("%s\n", str.cstr);

So one would think that the following is also possible:

char* plainstr = malloc(str.len + 1);
strcpy(plainstr, str);

But no go. In contrast to printf, strcpy is not variadic, so there's type checking. The compiler rightfully complains:

passing 'str_t' to parameter of incompatible type 'const char *'

But trying to tell the compiler "I really mean it" by casting it:

strcpy(plainstr, (const char*)str);

Won't work either:

operand of type 'str_t' where arithmetic or pointer type is required

Note that the following can't work:

strcpy(plainstr, (const char*)&str);

Since str.cstr != &str. For example, the output of this:

printf("%p %p\n", str.cstr, &str);

Is the following:

0xbdb010 0x7fff788f6ab8

And indeed, garbage data is being copied to plainstr.

So the questions are:

  1. Why isn't it allowed to cast a struct to a pointer type?
  2. How come that printf deals with this correctly if casting isn't allowed?

Upvotes: 3

Views: 4209

Answers (1)

user529758
user529758

Reputation:

Why isn't it allowed to cast a struct to a pointer type?

Because it makes no sense. How would you reinterpret a whole bunch of possibly unrelated information of distinct types as a concise memory address? However, in the previous question you asked, all of the people who answered, cited the C standard, and one particular statement in the standard stated that

The address of the structure is the address of its first element

So (as @Mat already pointed it out), you can indeed write

strcpy(destination, *(const char **)&str);

and that "will work" for the reasons I just enumerated.

How come that printf deals with this correctly if casting isn't allowed?

Because in C, typecasting is often just for fooling the compiler (except when it isn't). By passing the structure, the structure will be copied, and your stack will be something like (I'm intentionally omitting any padding from the structure for the sake of simplicity):

> top of the stack: pointer to the format string
> address of the copied struct *and*  address of the copy of the char pointer
> address of the length of the string (size_t)
> every other stuff

So, now what printf() will do is:

  • pop off the first value off the stack. It will be the format string.
  • Now when it encounters the %s format specifier in the format string, it will pop off another char pointer - in reality, it's the pointer to the structure, and the pointer to the first element, which is the string to be printed.
  • So it happily prints the string and returns.

Also, this is still undefined behavior, despite the fact that it works - if you don't specify a format string for printf() that actually corresponds to the types you pass in as its variadic arguments, that's not conformant and you can expect anything to happen.

Upvotes: 2

Related Questions