Reputation: 51832
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:
printf
deals with this correctly if casting isn't allowed?Upvotes: 3
Views: 4209
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:
%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.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