5

I'm implementing iterator through continuous chunk of memory and came to the issue about its conforming usage. My current implementation (assuming I'm iterating through array of chars).

typedef struct iterator{
    void *next_ptr;
    void *limit; //one past last element pointer
} iterator_t;

void *next(iterator_t *iterator_ptr){
    void *limit = iterator_ptr -> limit;
    void *next_ptr = iterator_ptr -> next_ptr;
    ptrdiff_t diff = limit - next_ptr;
    if(diff <= 0){
        return NULL;
    }
    iterator_ptr -> next_ptr = ((char *) next_ptr) + 1;
    return next_ptr;
}

The issue is the Standard claims at 6.5.6(p9) that:

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

This is true. I assume the area I'm iterating through is an array.

If the result is not representable in an object of that type, the behavior is undefined. In other words, if the expressions point to, respectively, the i-th and j-th elements of an array object, the expression (P)-(Q) has the value i−j provided the value fits in an object of type ptrdiff_t.

The limits of ptrdiff_t are defined at 7.20.3(p2):

limits of ptrdiff_t

PTRDIFF_MIN −65535

PTRDIFF_MAX +65535

There is no any guarantees that all values represented with size_t should be represented with ptrdiff_t.

So we judging by the limits we can conformingly subtract pointers of an array only of 65535 elements at most? So this would not work in general case where I want to subtract two pointers to elements of an array of unknown size?

AShelly
  • 34,686
  • 15
  • 91
  • 152
Some Name
  • 8,555
  • 5
  • 27
  • 77
  • Where exactly have you seen `PTRDIFF_MIN −65535` and `PTRDIFF_MAX +65535`? These limits are platform dependent, but 65535 seems tiny to me. – Jabberwocky Mar 26 '19 at 15:59
  • `SIZE_MAX` should agree with `PTRDIFF_MIN/MAX` on the specific implementation to make sense. – Eugene Sh. Mar 26 '19 at 16:00
  • 1
    -65535 to +65535 is the _guaranteed minimum range_, actual ranges are of course higher on current systems. – Ctx Mar 26 '19 at 16:00
  • @Jabberwocky These are in the standard, but it is the *minimal* magnitude required – Eugene Sh. Mar 26 '19 at 16:01
  • @EugeneSh. *`SIZE_MAX` should agree with `PTRDIFF_MIN/MAX`* Maybe not. The size of an object is unsigned. The difference has to be signed. – Andrew Henle Mar 26 '19 at 16:01
  • @AndrewHenle I did not say they have to have same value. – Eugene Sh. Mar 26 '19 at 16:02
  • @EugeneSh. That's what I'm asking. As far as I can tell the Standard does not provide any agreement on `ptrdiff_t`/`size_t` limits. The only their minimum value. So the only conforming way is to compare the size to the limit... Right? – Some Name Mar 26 '19 at 16:02
  • @EugeneSh. They *can't* have the same value if you assume `size_t` and `ptrdiff_t` are the same size. Given 32-bit values, there's no way to use `ptrdiff_t` to get the difference between any two pointers referencing a 3 GB array. – Andrew Henle Mar 26 '19 at 16:04
  • Just a remark. Even if unrelated to the question, pointer arithmetics on `void *` is not allowed per C standard (even if gcc is glad with it). The conformant way is to cast the pointers to `char *`. – Serge Ballesta Mar 26 '19 at 16:05
  • @EugeneSh._It is guaranteed that the difference between two pointers in the same object is fitting into ptrdiff_t_. I would argue about that. The Standard further states that _if the result is not representable in an object of that type, the behavior is undefined._ So I it is not necessary fir, isn't? Or my o interpretation is wrong? – Some Name Mar 26 '19 at 16:07
  • @SomeName Good point. Now I understand your question, as it looks like I missed it previously. – Eugene Sh. Mar 26 '19 at 16:08
  • @EugeneSh. The only way to go I see now is to convert pointers to convert pointers to `intptr_t` (if any) and substract the values. But the substraction is not guaranteed to have same value as would difference of indices have. – Some Name Mar 26 '19 at 16:16
  • 1
    *The only way to go I see now is to convert pointers to convert pointers to `intptr_t`* That won't work. You have to convert to `uintptr_t` or the resulting difference can be incorrect. Then you run into the same problem of not having enough bits to represent the same magnitude along with a direction for the difference. – Andrew Henle Mar 26 '19 at 16:42
  • @AndrewHenle Have to agree, yes. – Some Name Mar 26 '19 at 16:48
  • 1
    Unrelated to the main question, but isn't subtraction of two `void*` undefined? You can't have an array of `void` objects. – AShelly Mar 26 '19 at 17:00
  • @AShelly Exactly. _both operands are pointers to qualified or unqualified versions of compatible complete object types_. My mistake... Conversion to `char *` should work. – Some Name Mar 26 '19 at 17:29

2 Answers2

5

From the specification (section 7.20.3)

Its implementation-defined value shall be equal to or greater in magnitude (absolute value) than the corresponding value given below

[Emphasis mine]

So the values mentioned are only minimum values. The implementation could have larger limits. I would expect ptrdiff_t to be the word-length of the target platform (i.e. a 64-bit type of 64-bit systems).

And note that size_t is an unsigned integer type, while ptrdiff_t is a signed integer type. That kind of implies that not all values of a size_t could be represented by a ptrdiff_t.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Can you please elaborate on the following example: We `malloc`ed `SIZE_MAX` bytes as `char *bytes = malloc(SIZE_MAX)`. So pointer to the last element would be `bytes + SIZE_MAX`. Isn't `ptrdiff_t diff = (bytes + SIZE_MAX) - bytes` overflow guaranteely? – Some Name Mar 26 '19 at 16:12
  • @SomeName On a system where `SIZE_MAX > PTRDIFF_MAX` then that would undoubtedly lead to an overflow. – Some programmer dude Mar 26 '19 at 16:17
  • That was the point I wanted to understand, thanks. Actually this is true for the system I currently use `#define PTRDIFF_MAX 9223372036854775807L` and `#define SIZE_MAX 18446744073709551615UL`. – Some Name Mar 26 '19 at 16:27
  • It would be cool if you can give an advice of conforming usage of `ptrdiff_t`. `#if` macro would not work with `malloc`ed memory where the size to be allocated is not a constant expression... – Some Name Mar 26 '19 at 16:35
  • 1
    @SomeName Don't use such large arrays? ;) For normal everyday use, these limitations would never be relevant. If the limits are getting close, then arrays are probably not the best data-structure anyway. – Some programmer dude Mar 26 '19 at 17:10
3

This seems to be a problem in the C standard itself.

As you noted, 6.5.6 Additive operators, paragraph 9 states, in part:

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; the result is the difference of the subscripts of the two array elements. The size of the result is implementation-defined, and its type (a signed integer type) is ptrdiff_t defined in the <stddef.h> header. If the result is not representable in an object of that type, the behavior is undefined. In other words, if the expressions P and Q point to, respectively, the i-th and j-th elements of an array object, the expression (P)-(Q) has the value i-j provided the value fits in an object of type ptrdiff_t. ...

There appears to be no guarantee in the C standard that you can represent the difference of two pointers in a ptrdiff_t.

Realistically, this would mean that a ptrdiff_t has to be larger than a size_t. A size_t only has to cover magnitude in a fixed number of bits. ptrdiff_t has to cover both magnitude and direction. If sizeof( size_t ) == sizeof( ptrdiff_t ), then there's no guarantee that the undefined behavior in 6.5.6p9 won't be invoked.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Actually the UB can be easily invoked. `char *p = (char *) malloc(SIZE_MAX); ptrdiff_t diff = (p + SIZE_MAX) - p; printf("ptrdiff_t = %td\n", diff);` prints -1 on my machine. – Some Name Mar 26 '19 at 16:56
  • 2
    Note that this is only a problem if the size of the largest object which can be created is larger than `PTRDIFF_MAX-1`, which is not the case on any 64-bit implementation I know of. There are implementations with 32-bit `size_t` and `ptrdiff_t` which allow single objects bigger than 2GB, and on those implementations overflow is possible (at least for `char*` pointer subtraction). – rici Mar 26 '19 at 19:45
  • @rici *There are implementations with 32-bit `size_t` and `ptrdiff_t` which allow single objects bigger than 2GB, and on those implementations overflow is possible (at least for char* pointer subtraction).* Those are the exact types of implementations that popped into my head when I understood what the OP was asking. I'm surprised the question only has 3 upvotes. – Andrew Henle Mar 26 '19 at 21:40
  • 1
    The question comes up from time to time, which might explain that. Here's a couple of examples, maybe dupes: https://stackoverflow.com/q/49380475 https://stackoverflow.com/q/38442017 – rici Mar 27 '19 at 00:10