Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given a pointer to member a within a struct, write a routine that returns a pointer to the struct

Tags:

c

struct

Here is an interview question that I saw on some forum. I've been trying to figure out how it works but I don't quite get it. Could somebody explain how it works?

Q: Given a pointer to member a within a struct, write a routine that returns a pointer to the struct.

struct s 
{
   ...
   int a;
   …
};

struct s *get_s_ptr(int *a_ptr)
{
   // implement this.
}

The answer is:

struct s* get_s_ptr(int *a_ptr)
{
   return (struct s*)((char*)a_ptr - (int)&((struct s*)0)->a);
}
like image 231
Steve Avatar asked Jun 20 '10 02:06

Steve


2 Answers

How does it work?

The fundamental equation here (all arithmetic in bytes) is

address of struct member s->a == s + byte offset of a

Given the type of s, a single compiler, and a single target machine, they determined the byte offset of a—it's the same for every struct of type s.

You're given the left-hand side and your interviewer asked you to recover s. You can do this by getting a new equation; subtract the byte offset from both sides:

address of struct member s->a - byte offset of a == s

In the problem, you're given the address of s->a, but you have to figure out the byte offset. To do this you use the original equation again with s set to zero:

address of struct member s->a where s is zero == zero + byte offset of a 
                                              == byte offset of a

The left-hand side in C is built as follows

struct pointer s where s is zero                            (struct s *)0
struct member s->a where s is zero                          ((struct s*)0)->a
address of s->a where s is zero                             &((struct s*)0)->a

Final steps:

  1. To make the arithmetic legal C this byte offset is cast to an integer.
  2. To make sure the subtraction is done in units of bytes, a_ptr is cast to char *.
  3. To give the result the right type the difference is cast to struct s *.

Addendum: As Eli Bendersky points out, you should try to avoid situations where this code would be necessary. There is almost always a better way.

like image 136
Norman Ramsey Avatar answered Sep 28 '22 08:09

Norman Ramsey


The answer is: it doesn't. It doesn't work, even if it might seem to "work" at the first sight. The "answer" makes an attempt to dereference a null pointer, which leads to undefined behavior. So, unless your idea of "working" includes undefined behavior, that answer does not work.

There are more problems with that solution, besides the attempt to derefercence a null pointer (although that alone is perfectly enough to throw that "answer" to garbage bin). Another problem is that the result of (struct s*) 0 is a null pointer of struct s * type. The language makes no guarantees about the actual physical value of a null pointer. If could easily be something like 0xBAADFOOD, which would immediately screw up the intended functionality of the "answer".

The proper implementation of the implied technique would involve the standard offsetof macro (already suggested in Nyan's answer, but I'll repeat it one more time)

struct s* get_s_ptr(int *a_ptr)
{
   return (struct s*) ((char *) a_ptr - offsetof(struct s, a));
}
like image 38
AnT Avatar answered Sep 28 '22 06:09

AnT