2

I have a question about the following simple comparison:

#define BUF_SIZE //maybe large

static char buf[BUF_SIZE];

static char *limit; // some pointer to an element of buf array

void foo(){
    if(limit - buf <= sizeof buf){  //<---- This comparison
        //...
    }
    //...
}

Here we are comparing ptrdiff_t (on the left) which is signed and size_t (on the right) which is unsigned. The Standard provides the following explanation

6.5.8/3:

If both of the operands have arithmetic type, the usual arithmetic conversions are performed.

6.3.1.8/1 gives us 3 possibilities:

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

We don't know the conversion rank of ptrdiff_t and size_t. Moreover there is no in general a corresponding unsigned type for ptrdiff_t (unlike, say intptr_t and uintptr_t).

QUESTION: Suppose that the conversion rank of ptrdiff_t is strictly greater than of size_t and ptrdiff_t cannot represent all the values of size_t. What will happen when performing comparison between ptrdiff_t and size_t providing that there is no corresponding unsigned integer type for ptrdiff_t. Is such an implementation even allowed?

St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • Why is 6.2.5/6 not applicable? It clearly says that there must be an unsigned type corresponding to `ptrdiff_t`. We don't know what its name is, but it must exist. – rici Dec 24 '19 at 06:26
  • @rici the clause defines the corresponding unsigned type using the keyword `unsigned`. But according to 6.7.2/2 `unsigned ptrdiff_t` is not a valid type specifier. – St.Antario Dec 24 '19 at 06:31
  • St.Antario: No. If `ptrdiff_t` is a type alias, the corresponding unsigned type is the corresponding unsigned type of the type `ptrdiff_t` is an alias of. Type aliases are not different types; they are different names for the same type. That's why I said we don't know the name of the corresponding unsigned type. The fact that we don't know how to declare it does not mean that it doesn't exist. – rici Dec 24 '19 at 06:43
  • In fact, `ptrdiff_t` might be an alias of an extended integer type, whose name is in the part of the namespace reserved for the implementation. But it still will have a corresponding unsigned type. – rici Dec 24 '19 at 06:45
  • @rici But the Standard does not specify `ptrdiff_t` to be exactly a type alias of a standard integer type. In case it is then yes, there is a corresponding unsigned type. Otherwise it is not specified. – St.Antario Dec 24 '19 at 06:46
  • Yes, it does. It says that `ptrdiff_t` is a "signed integer type", which is a phrase precisely defined by the standard. (6.5.6/9). (Definition at 6.2.5/4: "The standard and extended signed integer types are collectively called signed integer types." – rici Dec 24 '19 at 06:48
  • @rici As you said an Impelemtation can define `ptrdiff_t` to be an alias of an extended integer type. From 6.5.2/6 _For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned)_. What is the reason for _designated with the keyword unsigned_ then? 6.7.2/2 does not allow `unsigned` type specifier with anything but standard integer types. – St.Antario Dec 24 '19 at 06:57
  • if taken literally, that would mean you couldn't declare a variable of that type. But an implementation is actually free to implement any extension it likes, at least as long as the extended names are in the extension namespace (eg. `_Ptrdiff`, with corresponding type `unsigned _Ptrdiff`). But I repeat, even if you cannot name the unsigned type, *it exists*. The existence of a type is not contingent on it having a name. – rici Dec 24 '19 at 07:02
  • After having this discussion, I think 6.7.2/2 needs a footnote, at least. But I think the intent is clear. – rici Dec 24 '19 at 07:04
  • @rici If the wording regarding `unsigned` keyword means that we cannot declare a variable of that type then I think it is kind of redundant. If the Standard does not specify a corresponding unsigned type then we cannot declare a variable of that type anyway because there is no type specifier. We have `intptr_t` `uintptr_t` specified explicitly and the only `ptrdiff_t`. – St.Antario Dec 24 '19 at 07:13
  • 1
    The various idiosyncracies of `size_t` and `ptrdiff_t` have been discussed extensively over a span of more than two decades. Here, on mailing lists, on blogs, and most importantly in committee submissions. I don't think there's a high likelihood that another discussion will reveal anything new. If you are interested in implementations with extended integer types, you can certainly find them. I don't know if there is yet a consensus about the base names of such types; my sense is that type aliases are common. – rici Dec 24 '19 at 07:38

1 Answers1

2

If ptrdiff_t is of greater rank than size_tand can represent all positive values of size_t. limit - buf <= sizeof buf poses no problems then. The compare is done as ptrdiff_t.

Else ptrdiff_t may not represent all positive values of size_t and then the subtraction limit - buf may be UB per below, so the compare is moot.

J.2 Undefined behavior
The behavior is undefined in the following circumstances:
...
The result of subtracting two pointers is not representable in an object of type ptrdiff_t (6.5.6).


Is such an implementation even allowed? (conversion rank of ptrdiff_t is strictly greater than of size_t and ptrdiff_t cannot represent all the values of size_t)

Yes may be allowed as ptrdiff_t as long and size_t as unsigned. Both 32-bit. But perhaps not wise.


Note: C17 § 7.19 4 has

Recommended practice
The types used for size_t and ptrdiff_t should not have an integer conversion rank greater than that of signed long int unless the implementation supports objects large enough to make this necessary.

Not that this applies a lot here - just a note.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • _Yes may be allowed as ptrdiff_t as long and size_t as unsigned._ In this case there is a corresponding unsigned integer type for `ptrdiff_t`. The Standard does not specify that `ptrdiff_t` should be a `typedef`. In case `ptrdiff_t` has the same representation as `long` but without a corresponding unsigned integer type will we get UB when doing such comparison? – St.Antario Dec 24 '19 at 05:28
  • @St.Antario This answer does not rely on `ptrdiff_t` having a corresponding _unsigned_ type. In the first case, `limit - buf <= sizeof buf` is all done as `ptrdiff_t`. In the next case, `limit - buf` is UB with large `limit`. – chux - Reinstate Monica Dec 24 '19 at 05:37