0
struct student  
{  
      int roll_no;  
      int physics;  
      int chem;  
      float percentage;  
};

int main()  
{  
    struct student s = {1, 96, 98, 97.00};  
    printf("%d", *((int *) &s + 1));  
}

Here in the above code we know that 's' is a structure variable and what my question is

whether 's' is a decay pointer or a separate stack frame is getting allocated for the structure, if it is a decay pointer we don't have to put that '&' with the 's' so i'm guessing it must be a stackframe cause it behaves just like a normal variable.

if we print 's' first members value gets printed and if we print '&s' first members address gets printed.

If it is anything other than this kindly help us to understand that too.

And also,

How to access if a Char array is a member of the structure, tried this *((char*)&s + offset_value); not working.

Your help will be much appreciated.

  • 1
    Can you not just use '.' to access the fields instead of pointers? – Martin James Oct 25 '21 at 03:52
  • 1
    Casting a structure is undefined behavior in C. This means: "(int *) s" is undefined behavior, as "s" is not a primitive type (char, short, int, etc.). However, (int *) &s is perfectly fine, and will give you the first integer contained by the struct. – squidwardsface Oct 25 '21 at 03:55
  • By this way we will know what exactly is happening with a structure and will give us more confidence and knowledge about it. @squidwardsface please check for char array as member of the structure tried this `** *((char*)&s + offset_value); **` not working. ignore the first 2 and last 2 asteriks. – Praveen Kumar Karthikeyan Oct 25 '21 at 04:05
  • 1
    "whether 's' is a decay pointer or a separate stack frame is getting allocated for the structure, if it is a decay pointer we don't have to put that '&' with the 's' so i'm guessing it must be a stackframe cause it behaves just like a normal variable." This doesn't make any sense. `s` is a struct. It's not an array so it doesn't "decay". It's not a "stack frame". You seem to be mixing up a lot of wildly unrelated terms. – Lundin Oct 25 '21 at 06:32
  • btw.. is `*((int *) &s + 1)` undefined behavior? Assuming that there is no padding of course. I guess it is *not* because the pointer is moved within `s`. Alignment is correct, `int` is accessed through the valid pointer type so strict aliasing rule is satisfied. It there any reason to make it UB? – tstanisl Oct 25 '21 at 07:42
  • @tstanisl Yes, it is undefined behavior. You are not accessing an int, you are accessing a struct through an int pointer, which are incompatible. – Christian Gibbons Oct 25 '21 at 08:05
  • @ChristianGibbons, I'm not convinced by your argument. `*((int *) &s + 0)` is perfectly fine even though struct is accessed via pointer to `int`. Moreover, `s` contains member which type is `int`. Therefore compiler must assume that objects pointed by `int*` and `struct student*` may alias. – tstanisl Oct 25 '21 at 08:09
  • I do not have time right now for a full response with citation; I may try to find some time tomorrow for that. It may be accurate that you can alias a struct with another object that is compatible with the beginning of the struct, what you are doing in essence is taking a pointer to the first member of the struct. That is, your object is that first struct member, not the struct itself. If you increment your pointer past that first object, then you now have a pointer one past your object. Once you dereference that pointer that no longer points to your object, you invoke undefined behavior – Christian Gibbons Oct 25 '21 at 08:18
  • @tstanisl: `*((int *) &s + 0)` has defined behavior because C 2018 6.7.2.1 15 says that when a pointer to a structure (`&s`) is “suitably converted” (`(int *)`), it points to the initial member of a structure. So `(int *) &s` is equivalent to `&s.roll_no`. Then `+ 0` does not change it, and `*((int *) &s + 0)` is equivalent to `*&s.roll_no`, which is equivalent to `s.roll_no`. – Eric Postpischil Oct 25 '21 at 11:08
  • 1
    @tstanisl: In contrast, the behavior of `*((int *) &s + 1)` is not defined. As above, this is equivalent to `*(&s.roll_no + 1)`. `&s.roll_no + 1` is defined; it is a pointer to just beyond the end of `s.roll_no`. However, dereferencing it with `*` is not defined, because C 2018 6.5.6 8 says that if the result of `+` points beyond the end like this, “it shall not be used as the operand of a unary `*` operator that is evaluated.” In other words, even if we can deduce what is in memory where the pointer points, and it is another `int`, there is an explicit rule that says we cannot use `*` on it. – Eric Postpischil Oct 25 '21 at 11:12
  • 1
    @EricPostpischil, I still have doubts. I don't think that `*(&s.roll_no + 1)` is equivalent to `*((int*)&s + 1)`. The first one produces a pointer that originates from `s.roll_no` object which is `int`. Thus incremented pointer indeed cannot be dereferenced. On the other hand `((int*)&s + 1)` originates from object `s`, thus the resulting pointer does not "overflow" the original object. Assuming that `((int*)&s + 1)` is invalid would `memcpy` work with such a pointer? Could `(int*)((char*)&s + sizeof(int))` be valid then? – tstanisl Oct 25 '21 at 12:37
  • @tstanisl: The specification of the `+` operator has no provision for where the pointer “originates from”. It either is a pointer to a particular object or it is not. The specification of the operator says nothing about whether that object is in a larger structure not. – Eric Postpischil Oct 25 '21 at 12:41
  • @EricPostpischil, it carries some information about the origin like disallowing overflowing the "original" array object (treating `int` as `int[1]`) – tstanisl Oct 25 '21 at 12:44
  • 1
    @EricPostpischil, what about other issues. Would `memcpy` work with `((int*)&s + 1)`? Is `(int*)((char*)&s + sizeof(int))` valid for dereferencing? – tstanisl Oct 25 '21 at 12:47
  • @tsanisl `char *` is a special case. You can access any object as an array of character types. In that case, you'd be accessing the struct as a char array rather than accessing the first member, and so it is valid to point to and dereference anything within the struct when accessing it as a char array. – Christian Gibbons Oct 25 '21 at 14:21
  • @tstanisl: `memcpy` gets complicated. The C standard is not written with formal mathematics, so a variety of natural language interpretation is needed. I expect this has been discussed elsewhere, and I do not want to get into it in comments on this question. You might find other questions about it, or you could post a new one. – Eric Postpischil Oct 25 '21 at 14:53
  • Thank you so much everyone for the fantastic discussion and the fantastic solution (int*)((char*)&s + sizeof(int)). Working like a treat, couldnt ask for anything better. – Praveen Kumar Karthikeyan Oct 29 '21 at 08:53
  • @EricPostpischil i've posted it as a dedicated question at https://stackoverflow.com/q/69771966/4989451 – tstanisl Nov 12 '21 at 13:12

2 Answers2

3

whether 's' is a decay pointer…

s is a structure. Structures are not automatically converted to pointers the way arrays are. (Some people say “decay,” but this is a colloquial term and is inaccurate.)

… or a separate stack frame is getting allocated for the structure,…

With s defined as you show inside main, the compiler automatically allocates space for it. In common C implementations, the compiler uses stack space for this (except that optimization may result in alternatives including using processor registers instead, incorporating use of the structure into other expressions, or eliminating part or all of the structure entirely). If the compiler does use stack space for s, it uses space within the stack frame for main; it does not allocate a separate stack frame.

(Generally, a stack frame is the space on the stack used by one function, sometimes with some modifications such as “leaf” functions that do not call other routines and may not establish a full stack frame.)

printf("%d", *((int *) &s + 1));

*((int *) &s + 1) is not proper C code because its behavior is not defined by the C standard and likely not by your compiler either.

&s is the address of the structure s. When we convert it to (int *), that gives the address of the first member of the structure, s.roll_no. That is because the C standard gives us a rule that says we can convert a pointer to a structure to a pointer to its first member. C 2018 6.7.2.1 15 says:

… 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…

Then you add 1 to this pointer, (int *) &s + 1. That also has defined behavior. From above (int *) &s points to s.roll_no, so it is equivalent to &s.roll_no. Then (int *) &s + 1 is equivalent to &s.roll_no + 1. C 2018 6.5.6 specifies what happens with addition. I will omit specifics of the rules, but essentially &s.roll_no + 1 is defined and produces a pointer that points just beyond s.roll_no.

However, even if the next member of the structure, physics, is just beyond s.roll_no1, the C standard does not define what happens when * is applied to it. This is because C 2018 6.5.6 8 says:

If the result [of the addition] points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

In the context of 6.5.6, s.roll_no is an “array object” that contains just one element, and &s.roll_no + 1 points one beyond its last element. So *(&s.roll_no + 1) and, equivalently, *((int *) &s + 1) violates this rule. C 2018 4 2 says that when a rule like this is violated, the behavior is not defined:

If a "shall" or "shall not" requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined…

Footnote

1 This is not guaranteed because C implementations are allowed to insert padding between members in structures. However, typical C implementations will not insert padding between members of identical types, as it is not needed for alignment, and this can be checked at compile time. But the statements above apply even if there is no padding.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
-1

Structs parameters are passed by value unless the & operator is used. Arrays parameters are passed as pointers. A local variable is in the stack frame (or a register).

You might find the offsetof macro interesting

#define offsetof(S, F) (((char *)&((S *)0)->F - (char *)0))
dirck
  • 838
  • 5
  • 10
  • Thank you so much @dirck you are a life savior – Praveen Kumar Karthikeyan Oct 25 '21 at 05:32
  • And a stackframe can have more than one data in it. Just like a structure, can we confirm on this. – Praveen Kumar Karthikeyan Oct 25 '21 at 05:57
  • Typically, an invocation of a function creates a (single) stack frame for all the local variables and parameters - parameters on one side of the frame pointer, and locals on the other. If a function has no locals and no parameters it may not explicitly create a frame. A compiler may optimize this away as well. – dirck Oct 25 '21 at 06:01
  • Okay a structure variable is also a part of the stackframe, so what can you say about its behaviour. – Praveen Kumar Karthikeyan Oct 25 '21 at 06:24
  • `offsetof` is already required by C standard and it can be found in `stddef.h`. There is no need to reinvent it – tstanisl Oct 25 '21 at 07:39
  • I thought reading a definition might be instructive. There's also the wikipedia article https://en.wikipedia.org/wiki/Offsetof – dirck Oct 25 '21 at 15:11