12
char *buf = malloc(bufsize)
char *ptr = buf;
…
while(condition) {
    ptrdiff_t offset = ptr - buf;    // <========== THIS LINE

    // offset will never be negative because we only ever *increase* ptr
    if ((size_t)offset > bufsize) {
        // we need more room
        bufsize += 128;
        buf = realloc(buf, bufsize);
        ptr = buf + offset;  // buf might be in a completely new location
    }
    *ptr++ = …  // write this byte
}

Is this valid or undefined?

I would have assumed that it's valid, but I read something about it being undefined, so I googled it. These links seem to inescapably claim it's undefined:

However, no mention of it is made in these SO questions:

These all talk about not two pointers being in the same "array". Does that actually mean a plain old C array on the stack?

If it is undefined, it seems very odd to me… Why force me to carry along a counter variable when I have access to one constant pointer and one moving pointer?

Community
  • 1
  • 1
mk12
  • 25,873
  • 32
  • 98
  • 137

3 Answers3

7

Pointers into a block of memory returned by malloc count as being into the same array:

7.22.3 Memory management functions

1 - The pointer returned [from malloc] if the allocation succeeds [...] may be assigned to a pointer to any type of object [...] and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thanks. I wasn't sure whether this was just a case of different terminology being used, or if I was just missing something. – mk12 Aug 23 '12 at 20:52
  • Some parts of the Standard would suggest that, given something like `struct foo char arr[5][5]; } *p = malloc(25);`, the lvalue `p->arr[x][y];` wouldn't be defined for any values of `y` outside the range 0..4, regardless of the value of `x`, even if the indexed item would be within the same allocated region. It's unclear what one would have to do to `p->arr[y]` to get a pointer that could be indexed to all addresses within the region. – supercat Dec 09 '19 at 07:45
2
ptrdiff_t offset = ptr - buf;    // <========== THIS LINE

This is perfectly defined behavior.

(C99, 6.5.6p9) "When two pointers are subtracted, both shall point to elements of the same array object [...]"

ouah
  • 142,963
  • 15
  • 272
  • 331
  • Yes, but it's that "…elements of the same **array object** …" that has got me confused. Is a malloc'd chunk of memory an "array object"? – mk12 Aug 23 '12 at 21:03
  • ecatmur is quoting the relevant paragraph of the standard in his answer. – ouah Aug 23 '12 at 21:12
  • as a side note, even this is defined: `int a = 0; int *p = &a; int *q = &a + 1; ptrdiff_t d = p - q;` – ouah Aug 23 '12 at 21:14
2

It's defined behavior, as long as you don't go farther than one element past the end of the array. C99 §6.5.6/8 says this about adding a pointer and an integer:

[...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. [...]

And paragraph 9, on subtraction:

9) When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; [...]

And from §7.20.3/1:

The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

So once you move ptr to point beyond the element after the last array element, doing the pointer subtraction is undefined behavior.

I do believe there are systems out there that would behave badly with this code, although I can't name any. In theory, malloc() could return a pointer to just before the end of addressable memory, e.g. if you ask for 255 bytes it could return 0xFFFFFF00 on a 32-bit system, so creating a pointer beyond the end will lead to overflow. It's also possible that integer overflow on pointer representations can trigger a trap of some sort (e.g. if pointers are stored in special registers). While I don't know of any systems with these properties, the C standard certainly allows for their existence.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • *So once you move ptr to point beyond the element after the last array element, doing the pointer subtraction is undefined behavior.* Except when `ptr` is one past the last array element. – ouah Aug 23 '12 at 21:18
  • *In theory, malloc could return a pointer to just before the end of addressable memory, e.g. if you ask for 256 bytes it could return 0xFFFFFF00 on a 32-bit system* actually it could not because of the *one past the last array element* rule. C Standard says in a non-normative footnote: *When viewed in this way, an implementation need only provide one extra byte (which may overlap another object in the program) just after the end of the object in order to satisfy the ‘‘one past the last element’’ requirements.* – ouah Aug 23 '12 at 21:24
  • 2
    Sorry, I missed the part that was only incrementing one byte at a time, see update. Going one byte it a time is OK, but if you were to jump in larger increments and go farther than one past the end, it's undefined behavior. – Adam Rosenfield Aug 23 '12 at 21:29