2

I am reading the source code of redis. Here is the code:

typedef char *sds;

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

About this code, I have some issue:

  1. Why is the output of sizeof(struct sdshdr) 8? Why is char buf[] not included?
  2. I can't understand the functions size_t sdslen and sdsavail. Why do struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));?
Mat
  • 202,337
  • 40
  • 393
  • 406
BlackMamba
  • 10,054
  • 7
  • 44
  • 67
  • It's a [Flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member) – David Ranieri Feb 24 '16 at 08:53
  • The casts make no sense. You can't safely convert from a character pointer to a struct pointer, it violates strict aliasing. Furthermore, it is very bad practice to hide pointers behind typedefs. – Lundin Feb 24 '16 at 08:59
  • [Possible duplicate](http://stackoverflow.com/questions/31318740/internal-mechanism-of-sizeof-in-c). – Lundin Feb 24 '16 at 09:00
  • @Lundin That's what the `(void *)` casts fix, right? The incoming pointer is `char *s`, and you can subtract from that. Then the result is cast to `void *`, which converts fine to `struct shshdr *` (but is shorter and more general). – unwind Feb 24 '16 at 09:32
  • @Lundin I'm pretty sure there is no string aliasing problem here because the pointer's value has changed and is pointing to an actual struct object after the subtraction. It would be for example if the string itself would be reinterpreted as a struct, but it isn't. Struct is interpreted as a struct using the compatible type. (char* is also allowed to point to any object.) – 2501 Feb 24 '16 at 10:09
  • It is is unclear to me whether this violates 6.5.6. paragraph 8. – 2501 Feb 24 '16 at 10:28

2 Answers2

1
  1. The 0-sized array has no size, it's declared length is 0. This is a flexible array member; it can only appear at the end of a struct.
  2. That initialization sets sh to point at memory computed by taking the value of s, a char *, and subtracting the size of the sds header. In other words, from a pointer to the first character of a string (the flexible array member) we compute a pointer to the header itself so we can get at the length.
unwind
  • 391,730
  • 64
  • 469
  • 606
1

  • char buf[] hasn't allocated any memory so it doesn't take up space therefore acting as a flexible array therefore 2 int datatype ends up taking 4+4 = 8 bytes.

  • in size_t, the variable passed is s that belongs to *sds which is a typedef of a char

    This leads to the implementation of this code in memory.

    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
      } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
      }
      if (sh == NULL) return NULL;
      sh->len = initlen;
      sh->free = 0;
      if (initlen && init)
        memcpy(sh->buf, init, initlen);
      sh->buf[initlen] = '\0';
      return (char*)sh->buf;
    }
    

    which stores memory space equal to the sh sdshdr struct.

  • Martin Gardener
    • 1,001
    • 8
    • 14
    • Could you give me a detailed explanation about `struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));`? i can't understand it. – BlackMamba Feb 24 '16 at 09:20
    • `s` is a pointer in memory, which seems to point just after a `struct sdshdr`, then `s-(sizeof(...)` computes the address of this *just before* struct. That could have been written as `((struct sdshdr *)s)-1`. – Jean-Baptiste Yunès Feb 24 '16 at 09:50
    • 1
      @BlackMamba: what this line does, it create a memory space for sh = malloc/calloc, After that it checks if that char *s intial len and adds 1 to the end. It then sets its complete length and checks if memory of that line is available to initialize, after initializing it appends a '\0' for end of *s, after that it returns the buffer to the program which must be a type of 'sds' which is char* therefore the return statement ends up return char* sh->buf. Hope this is made clear, upvote if your problem is cleared :) – Martin Gardener Feb 24 '16 at 19:49
    • @DanyalImran I found this link, i am clear with it now: http://redis.io/topics/internals-sds – BlackMamba Feb 29 '16 at 05:27
    • @BlackMamba thats great :D – Martin Gardener Feb 29 '16 at 13:15