A legal array assignment. Is it possible? - c

After reading the chapter about structures in the K&R book I decided to make some tests to understand them better, so I wrote this piece of code:
#include <stdio.h>
#include <string.h>
struct test func(char *c);
struct test
{
int i ;
int j ;
char x[20];
};
main(void)
{
char c[20];
struct {int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b;
c = func("Another string").x;
printf("%s\n" , c);
}
struct test func(char *c)
{
struct test temp;
strcpy(temp.x , c);
return temp;
}
My question is: why is c = func("Another string").x; working (I know that it's illegal, but why is it working)? At first I wrote it using strcpy() (because that seemed the most logical thing to do) but I kept having this error:
structest.c: In function ‘main’:
structest.c:16:2: error: invalid use of non-lvalue array

char c[20];
...
c = func("Another string").x;
This is not valid C code. Not in C89, not in C99, not in C11.
Apparently it compiles with the latest gcc versions 4.8 in -std=c89 mode without diagnostic for the assignment (clang issues the diagnostic). This is a bug in gcc when used in C89 mode.
Relevant quotes from the C90 Standard:
6.2.2.1 "A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type. and if it is a structure or union. does not have any member (including. recursively, any member of all contained structures or unions) with a const-qualified type."
and
6.3.16 "An assignment operator shall have a modifiable lvalue as its left operand."
6.3.16 is a constraint and imposes at least for gcc to issue a diagnostic which gcc does not, so this is a bug.

It's a bug in gcc.
An expression of array type is, in most contexts, implicitly converted to a pointer to the first element of the array object. The exceptions are when the expression is (a) the operand of a unary sizeof operator; (b) when it's the operand of a unary & operator; and (c) when it's a string literal in an initializer used to initialize an array object. None of those exceptions apply here.
There's a loophole of sorts in that description. It assumes that, for any given expression of array type, there is an array object to which it refers (i.e., that all array expressions are lvalues). This is almost true, but there's one corner case that you've run into. A function can return a result of struct type. That result is simply a value of the struct type, not referring to any object. (This applies equally to unions, but I'll ignore that.)
This:
struct foo { int n; };
struct foo func(void) {
struct foo result = { 42 };
return result;
}
is no different in principle from this:
int func(void) {
int result = 42;
return result;
}
In both cases, a copy of the value of result is returned; that value can be used after the object result has ceased to exist.
But if the struct being returned has a member of array type, then you have an array that's a member of a non-lvalue struct -- which means you can have a non-lvalue array expression.
In both C90 and C99, an attempt to refer to such an array (unless it's the operand of sizeof) has undefined behavior -- not because the standard says so, but because it doesn't define the behavior.
struct weird {
int arr[10];
};
struct weird func(void) {
struct weird result = { 0 };
return result;
}
Calling func() gives you an expression of type struct weird; there's nothing wrong with that, and you can, for example, assign it to an object of type struct weird. But if you write something like this:
(void)func().arr;
then the standard says that the array expression func().arr is converted to a pointer to the first element of the non-existent object to which it refers. This is not just a case of undefined behavior by omission (which the standard explicitly states is still undefined behavior). This is a bug in the standard. In any case, the standard fails to define the behavior.
In the 2011 ISO C standard (C11), the committee finally recognized this corner case, and created the concept of temporary lifetime. N1570 6.2.4p8 says:
A non-lvalue expression with structure or union type, where the
structure or union contains a member with array type (including,
recursively, members of all contained structures and unions) refers to
an object with automatic storage duration and temporary lifetime
Its lifetime begins when the expression is evaluated and its initial
value is the value of the expression. Its lifetime ends when the
evaluation of the containing full expression or full declarator ends.
Any attempt to modify an object with temporary lifetime results in
undefined behavior.
with a footnote:
The address of such an object is taken implicitly when an array member is accessed.
So the C11 solution to this quandary was to create a temporary object so that the array-to-pointer conversion would actually yield the address of something meaningful (an element of a member of an object with temporary lifetime).
Apparently the code in gcc that handles this case isn't quite right. In C90 mode, it has to do something to work around the inconsistency in that version of the standard. Apparently it treats func().arr as a non-lvalue array expression (which might arguably be correct under C90 rules) -- but then it incorrectly permits that array value to be assigned to an array object. An attempt to assign to an array object, whatever the expression on the right side of the assignment happens to be, clearly violates the constraint section in C90 6.3.16.1, which requires a diagnostic if the LHS is not an lvalue of arithmetic, pointer, structure, or union type. It's not clear (from the C90 and C99 rules) whether a compiler must diagnose an expression like func().arr, but it clearly must diagnose an attempt to assign that expression to an array object, either in C90, C99, or C11.
It's still a bit of a mystery why this bug appears in C90 mode while it's correctly diagnosed in C99 mode, since as far as I know there was no significant change in this particular area of the standard between C90 and C99 (temporary lifetime was only introduced in C11). But since it's a bug I don't suppose we can complain too much about it showing up inconsistently.
Workaround: Don't do that.

This line
c = func("Another string").x;
with c being declared as
char c[20];
is not valid C in any version of C. If it "works" in your case, it is either a compiler bug or a rather weird compiler extension.
In case of strcpy
strcpy(c, func("Another string").x);
the relevant detail is the nature of func("Another string").x subexpression. In "classic" C89/90 this subexpression cannot be subjected to array-to-pointer conversion, since in C89/90 array-to-pointer conversion applied to lvalue arrays only. Meanwhile, your array is an rvalue, it cannot be converted to const char * type expected by the second parameter of strcpy. That's exactly what the error message is telling you.
That part of the language was changed in C99, allowing array-to-pointer conversion for rvalue arrays as well. So in C99 the above strcpy will compile.
In other words, if your compiler issues an error for the above strcpy, it must be an old C89/90 compiler (or a new C compiler run in strict C89/90 mode). You need C99 compiler to compile such strcpy call.

There are two error in you code:
main(void)
{
char c[20];
struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b;
c = func("Another string").x;// here of course number one
printf("%s\n" , c);
}
struct test func(char *c)
{
struct test temp;
strcpy(temp.x , c);
return temp; // here is number two , when the func finished the memory of function func was freed, temp is freed also.
}
Write you code like this:
main(void)
{
struct test *c;
struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b;
c = func("Another string");
printf("%s\n" , c->x);
free(c); //free memory
}
struct test * func(char *c)
{
struct test *temp = malloc(sizeof(struct test));//alloc memory
strcpy(temp->x , c);
return temp;
}

OP: but why is it working?
Because apparently when copying a field of a structure, only type and size matters.
I'll search for doc to back this up.
[Edit] Reviewing C11 6.3.2 concerning assignments, the LValue C, because it is an array, it is the address of that array that becomes the location to store the assignment (no shock there). It is that the result of the function is a value of an expression, and the sub-field reference is also a value of an expression. Then this strange code is allowed because it simple assigns the value of the expression (20-bytes) to the destination location&c[0], which is also a char[20].
[Edit2] The gist is that the result of the func().x is a value (value of an expression) and that is a legit assignment for a matching type char[20] on the left side. Whereas c = c fails for c on the right side (a char[20]), becomes the address of the array and not the entire array and thus not assignable to char[20]. This is so weird.
[Edit3] This fails with gcc -std=c99.
I tried a simplified code. Note the function func returns a structure. Typical coding encourages returning a pointer to a structure, rather than a whole copy of some big bad set of bytes.
ct = func("1 Another string") looks fine. One structure was copied en masse to another.
ct.x = func("2 Another string").x starts to look fishy, but surprisingly works. I'd expect the right half to be OK, but the assignment of an array to an array looks wrong.
c = func("3 Another string").x is simply like the previous. If the previous was good, this flies too. Interestingly, if c was size 21, the compilation fails.
Note: c = ct.x fails to compile.
#include <stdio.h>
#include <string.h>
struct test {
int i;
char x[20];
};
struct test func(const char *c) {
struct test temp;
strcpy(temp.x, c);
return temp;
}
int main(void) {
char c[20];
c[1] = '\0';
struct test ct;
ct = func("1 Another string");
printf("%s\n" , ct.x);
ct.x = func("2 Another string").x;
printf("%s\n" , ct.x);
c = func("3 Another string").x;
printf("%s\n" , c);
return 0;
}
1 Another string
2 Another string
3 Another string

Related

Is type punning via union pointer legal in C?

This question is inspired by the code from this question, copied below, which does illegal type punning via pointer:
# include <stdio.h>
int main()
{
char p[]={0x01,0x02,0x03,0x04};
int *q = p;
printf("%x",*q);
return 0;
}
My question is, is the following version of above code legal? I am most unsure about casting pointer to char to pointer to union containing char array. Plenty of questions about type punning here at SO, but I did not find a duplicate which covers using a pointer in this way.
#include <stdio.h>
#include <stdint.h>
union char_int {
char p[4];
int32_t q;
};
int main()
{
char p[]={0x01,0x02,0x03,0x04};
int *q = &(((union char_int *)p)->q);
printf("%x",*q);
return 0;
}
Related, I believe these bytes will form a legal int32_t value for all possible representations allowed by the standard, but if someone can confirm this extra detail, that'd be great too.
The meaning of "An object shall have its stored value accessed only by an lvalue expression that has one of the following types..." depends upon how one defines the words "object" and "by" as used in that rule. So far as I can tell, there has never been anything resembling consensus on what those words mean, beyond the fact that the authors of the Standard presumably expected implementations to try to interpret the rule sensibly. Note that under a literal interpretation of the rule, something like:
short volatile x;
int test(void)
{
int y = x+1;
return y;
}
would invoke UB because the lifetime of y begins when code enters test, which in turn happens before x is read, but it cannot receive a value until after x is read. Consequently, the value of y must change within its lifetime, but such action does not involve any lvalue expression of type int nor any other allowable type.
Clearly such an interpretation would be absurd, but a rule that omits straightforward cases on the presumption that implementations will know what to do can't be relied upon to consider more complicated ones. With regard to the construct at issue, some compilers would say that in an lvalue expression like someUnion.member = 23;, the union object is modified "by" the lvalue expression someUnion, but not necessarily make allowances for the possibility that such an object might be accessed elsewhere by an lvalue of member type, nor by lvalues of other union types containing the same member. Without any clarity on what the word "by" is supposed to mean, however, it's not really possible to characterize any particular interpretation as right or wrong.

Allocating memory for a part of structure

I have the following example
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
typedef struct test{
int a;
long b;
int c;
} test;
int main()
{
test *t = (test*) malloc(offsetof(test, c));
t -> b = 100;
}
It works fine, but Im not sure about it. I think I have UB here. We have a pointer to an object of a structure type. But the object of the structure type is not really valid.
I went through the standard and could not find any definition of this behavior. The only section I could find close to this one is 6.5.3.2:
If an invalid value has been assigned to the pointer, the behavior of
the unary * operator is undefined
But this is not really relevant since the pointer returned by malloc is completely valid.
Is there a reference in the standard explaining such a behavior? I'm using C11 N1570.
From C2011, paragraph 6.2.6.1/4:
Values stored in non-bit-field objects of any other object type consist of n x CHAR_BIT bits, where n is the size of an object of that type, in bytes.
Therefore, since the allocated object in your code is smaller than the size of a struct test, it cannot contain a value of an object of that type.
Now consider your expression t -> b = 100. C2011, paragraph 6.5.2.3/4 defines the behavior of the -> operator:
A postfix expression followed by the -> operator and an identifier designates a member of a structure or union object. The value is that of the named member of the object to which the first expression points [...].
(Emphasis added.) We've established that your t does not (indeed, cannot) point to a struct test, however, so the best we can say about 6.5.2.3/4 is that it does not apply to your case. There being no other definition of the behavior of the -> operator, we are left with paragraph 4/2 (emphasis added):
If a ''shall'' or ''shall not'' requirement that appears outside of a constraint or runtime- constraint is violated, the behavior is undefined. Undefined behavior is otherwise indicated in this International Standard by the words ''undefined behavior'' or by the omission of any explicit definition of behavior.
So there you are. The behavior of your code is undefined.
since the pointer returned by malloc is completely valid.
No the pointer is not "completely valid". Not at all.
Why do you think the pointer is "completely valid"? You didn't allocate enough bytes to hold an entire struct test - the pointer is not "completely valid" as there isn't a valid struct test object for you to access.
There's no such thing as a partial object in C. That's why you can't find it in the C standard.
It works fine
No, it doesn't.
"I didn't observe it blowing up." is not the same as "It works fine."
Your code doesn't do anything observable. Per the as-if rule the compiler is free to elide the entire thing and just return zero from main().

C array/pointer declaration syntax quiz

I thought I understood how C arrays, pointers and pointers to arrays worked, but now I stumbled upon a piece of working code which I don't understand:
int sum(const int * const buf, int len)
{
int result = 0;
const int (*p)[];
int n;
p = (const int(*)[]) buf;
// p = buf without cast gives compiler warning here (but works)
// p = (const int(*)[]) &buf; // Doesn't work! Segfault!
for( n=0; n<len; n++)
{
result += (*p)[n];
}
return result;
}
This actually works. But how? The question is: how does that assignment from "buf" to "p" work? I thought this declaration of p was similar to "pointer to pointer to int", so I would have expected a "&" necessesary at the assignment. Why is this not necessary?
In fact the following prototype also works (with the same function body above):
int sum(const int buf[const], int len)
Now it seems even more clear that the declaration of "p" adds one level of pointer re-direction compared to the declaration of "buf". Still ... the assignment works fine without any "&" necessary. Can someone explain it?
First of all, please note that this code is not good practice. The pointer conversion is fishy-looking and unusual, and in particular it casts away const qualifiers, which is bad practice. (Declaring a parameter as ...* const buf is pretty fishy to begin with though.)
This actually works. But how?
There is a pointer conversion from (qualified) int pointer to int array pointer. Given that these two pointer types do not have different representation or different alignment (highly unlikely but theoretically possible), the pointer conversion in itself is ok (as per C11 6.3.2.3/8).
What matters then is the effective type of the data, which is an (array of) int. Effective type is a formal C term used for determining what type that is actually stored at a location, no matter the pointer types used for accessing. As long as the data is accessed through a pointer type that is compatible with the effective type of what's stored there, the code should work fine.
The formal reason why this is fine is C11 6.5/7 ("the strict aliasing rule"):
An object shall have its stored value accessed only by an lvalue expression that has one of
the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
/--/
— an aggregate or union type that includes one of the aforementioned types among its
members
Where "aggregate" is C standard gibberish for arrays and structs. If we access the data through an expression of array type, where the array element type is a type/qualified type compatible with the actual type, int in this case, everything is fine.
As for how it works, it simply de-references an array pointer. p = (const int(*)[]) buf; says "threat what's stored in this variable as a pointer to array of integer (and qualifiers be damned)". Then (*p)[n] takes the array pointer, de-references it to get the actual array, then uses the array index.
p = buf without cast gives compiler warning here
If you compile with gcc -pedantic-errors you get a more correct error. The pointer types are not compatible and so the compiler must generate a diagnostic message here - since p = buf is not valid C.
p = (const int(*)[]) &buf; // Doesn't work! Segfault!
This is because what's stored at &buf is not an array of int, it is just the pointer buf itself, likely allocated on the stack as a parameter to your function.

Is it OK to access past the size of a structure via member address, with enough space allocated?

Specifically, is the following code, the line below the marker, OK?
struct S{
int a;
};
#include <stdlib.h>
int main(){
struct S *p;
p = malloc(sizeof(struct S) + 1000);
// This line:
*(&(p->a) + 1) = 0;
}
People have argued here, but no one has given a convincing explanation or reference.
Their arguments are on a slightly different base, yet essentially the same
typedef struct _pack{
int64_t c;
} pack;
int main(){
pack *p;
char str[9] = "aaaaaaaa"; // Input
size_t len = offsetof(pack, c) + (strlen(str) + 1);
p = malloc(len);
// This line, with similar intention:
strcpy((char*)&(p->c), str);
// ^^^^^^^
The intent at least since the standardization of C in 1989 has been that implementations are allowed to check array bounds for array accesses.
The member p->a is an object of type int. C11 6.5.6p7 says that
7 For the purposes of [additive operators] a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.
Thus
&(p->a)
is a pointer to an int; but it is also as if it were a pointer to the first element of an array of length 1, with int as the object type.
Now 6.5.6p8 allows one to calculate &(p->a) + 1 which is a pointer to just past the end of the array, so there is no undefined behaviour. However, the dereference of such a pointer is invalid. From Appendix J.2 where it is spelt out, the behaviour is undefined when:
Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary * operator that is evaluated (6.5.6).
In the expression above, there is only one array, the one (as if) with exactly 1 element. If &(p->a) + 1 is dereferenced, the array with length 1 is accessed out of bounds and undefined behaviour occurs, i.e.
behavior [...], for which [The C11] Standard imposes no requirements
With the note saying that:
Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
That the most common behaviour is ignoring the situation completely, i.e. behaving as if the pointer referenced the memory location just after, doesn't mean that other kind of behaviour wouldn't be acceptable from the standard's point of view - the standard allows every imaginable and unimaginable outcome.
There has been claims that the C11 standard text has been written vaguely, and the intention of the committee should be that this indeed be allowed, and previously it would have been alright. It is not true. Read the part from the committee response to [Defect Report #017 dated 10 Dec 1992 to C89].
Question 16
[...]
Response
For an array of arrays, the permitted pointer arithmetic in
subclause 6.3.6, page 47, lines 12-40 is to be understood by
interpreting the use of the word object as denoting the specific
object determined directly by the pointer's type and value, not other
objects related to that one by contiguity. Therefore, if an expression
exceeds these permissions, the behavior is undefined. For example, the
following code has undefined behavior:
int a[4][5];
a[1][7] = 0; /* undefined */
Some conforming implementations may
choose to diagnose an array bounds violation, while others may
choose to interpret such attempted accesses successfully with the
obvious extended semantics.
(bolded emphasis mine)
There is no reason why the same wouldn't be transferred to scalar members of structures, especially when 6.5.6p7 says that a pointer to them should be considered to behave the same as a pointer to the first element of an array of length one with the type of the object as its element type.
If you want to address the consecutive structs, you can always take the pointer to the first member and cast that as the pointer to the struct and advance that instead:
*(int *)((S *)&(p->a) + 1) = 0;
This is undefined behavior, as you are accessing something that is not an array (int a within struct S) as an array, and out of bounds at that.
The correct way to achieve what you want, is to use an array without a size as the last struct member:
#include <stdlib.h>
typedef struct S {
int foo; //avoid flexible array being the only member
int a[];
} S;
int main(){
S *p = malloc(sizeof(*p) + 2*sizeof(int));
p->a[0] = 0;
p->a[1] = 42; //Perfectly legal.
}
C standard guarantees that
§6.7.2.1/15:
[...] A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
&(p->a) is equivalent to (int *)p. &(p->a) + 1 will be address of the element of the second struct. In this case, only one element is there, there will not be any padding in the structure so this will work but where there will be padding this code will break and leads to undefined behaviour.

Pointer vs array in C, non-trivial difference

I thought I really understood this, and re-reading the standard (ISO 9899:1990) just confirms my obviously wrong understanding, so now I ask here.
The following program crashes:
#include <stdio.h>
#include <stddef.h>
typedef struct {
int array[3];
} type1_t;
typedef struct {
int *ptr;
} type2_t;
type1_t my_test = { {1, 2, 3} };
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
type1_t *type1_p = &my_test;
type2_t *type2_p = (type2_t *) &my_test;
printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0
printf("my_test.array[0] = %d\n", my_test.array[0]);
printf("type1_p->array[0] = %d\n", type1_p->array[0]);
printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes
return 0;
}
Comparing the expressions my_test.array[0] and type2_p->ptr[0] according to my interpretation of the standard:
6.3.2.1 Array subscripting
"The definition of the subscript
operator [] is that E1[E2] is
identical to (*((E1)+(E2)))."
Applying this gives:
my_test.array[0]
(*((E1)+(E2)))
(*((my_test.array)+(0)))
(*(my_test.array+0))
(*(my_test.array))
(*my_test.array)
*my_test.array
type2_p->ptr[0]
*((E1)+(E2)))
(*((type2_p->ptr)+(0)))
(*(type2_p->ptr+0))
(*(type2_p->ptr))
(*type2_p->ptr)
*type2_p->ptr
type2_p->ptr has type "pointer to int" and the value is the start address of my_test. *type2_p->ptr therefore evaluates to an integer object whose storage is at the same address that my_test has.
Further:
6.2.2.1 Lvalues, arrays, and function designators
"Except when it is the operand of the
sizeof operator or the unary &
operator, ... , an lvalue that has
type array of type is converted to
an expression with type pointer to
type that points to the initial
element of the array object and is not
an lvalue."
my_test.array has type "array of int" and is as described above converted to "pointer to int" with the address of the first element as value. *my_test.array therefore evaluates to an integer object whose storage is at the same address that the first element in the array.
And finally
6.5.2.1 Structure and union specifiers
A pointer to a structure object,
suitably converted, points to its
initial member ..., and vice versa.
There may be unnamed padding within a
structure object, but not at its
beginning, as necessary to achieve the
appropriate alignment.
Since the first member of type1_t is the array, the start address of
that and the whole type1_t object is the same as described above.
My understanding were therefore that *type2_p->ptr evaluates to
an integer whose storage is at the same address that the first
element in the array and thus is identical to *my_test.array.
But this cannot be the case, because the program crashes consistently
on solaris, cygwin and linux with gcc versions 2.95.3, 3.4.4
and 4.3.2, so any environmental issue is completely out of the question.
Where is my reasoning wrong/what do I not understand?
How do I declare type2_t to make ptr point to the first member of the array?
Please forgive me if i overlook anything in your analysis. But i think the fundamental bug in all that is this wrong assumption
type2_p->ptr has type "pointer to int" and the value is the start address of my_test.
There is nothing that makes it have that value. Rather, it is very probably that it points somewhere to
0x00000001
Because what you do is to interpret the bytes making up that integer array as a pointer. Then you add something to it and subscript.
Also, i highly doubt your casting to the other struct is actually valid (as in, guaranteed to work). You may cast and then read a common initial sequence of either struct if both of them are members of an union. But they are not in your example. You also may cast to a pointer to the first member. For example:
typedef struct {
int array[3];
} type1_t;
type1_t f = { { 1, 2, 3 } };
int main(void) {
int (*arrayp)[3] = (int(*)[3])&f;
(*arrayp)[0] = 3;
assert(f.array[0] == 3);
return 0;
}
An array is a kind of storage. Syntactically, it's used as a pointer, but physically, there's no "pointer" variable in that struct — just the three ints. On the other hand, the int pointer is an actual datatype stored in the struct. Therefore, when you perform the cast, you are probably* making ptr take on the value of the first element in the array, namely 1.
*I'm not sure this is actually defined behavior, but that's how it will work on most common systems at least.
Where is my reasoning wrong/what do I not understand?
type_1::array (not strictly C syntax) is not an int *; it is an int [3].
How do I declare type2_t to make ptr point to the first member of the array?
typedef struct
{
int ptr[];
} type2_t;
That declares a flexible array member. From the C Standard (6.7.2.1 paragraph 16):
However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array.
I.e., it can alias type1_t::array properly.
It's got to be defined behaviour. Think about it in terms of memory.
For simplicity, assume my_test is at address 0x80000000.
type1_p == 0x80000000
&type1_p->my_array[0] == 0x80000000 // my_array[0] == 1
&type1_p->my_array[1] == 0x80000004 // my_array[1] == 2
&type1_p->my_array[2] == 0x80000008 // my_array[2] == 3
When you cast it to type2_t,
type2_p == 0x80000000
&type2_p->ptr == 0x8000000 // type2_p->ptr == 1
type2_p->ptr[0] == *(type2_p->ptr) == *1
To do what you want, you would have to either create a secondary structure & assign the address of the array to ptr (e.g. type2_p->ptr = type1_p->my_array) or declare ptr as an array (or a variable length array, e.g. int ptr[]).
Alternatively, you could access the elements in an ugly manner : (&type2_p->ptr)[0], (&type2_p->ptr)[1]. However, be careful here since (&type2_p->ptr)[0] will actually be an int*, not an int. On 64-bit platforms, for instance, (&type2_p->ptr)[0] will actually be 0x100000002 (4294967298).

Resources