tags:

views:

321

answers:

5
{
  char *a, *b;

  printf("%lx\n",(b-a));
}

Usually works, in fact, I can't imagine it giving a warning or failing on a 32-bit or 64-bit machine. But is that the proper thing to do for ANSI C and size awareness? I'd like this code to work on every platform possible, including non-Unixes and embedded systems.

+2  A: 

Since you haven't initialized the variables a and b, the code gives undefined behavior. But other than that, the type of b-a is ptrdiff_t, which is big enough to contain the result. If you have a modern enough C, you can printf it with %tx.

If you don't want to use %tx, you should convert your result so it actually matches (and not just by accident) your format specifier:

printf("%lx", (unsigned long)(a-b));

It is not inconceivable that a system could have for example a 32-bit address space, and a 32-bit ptrdiff_t, but a 64-bit long, and then your printf would fail.

Thomas Padron-McCarthy
No, size_t is unsigned.
Nikolai N Fetissov
@Nikolai: Thanks. I must be drunk.
Thomas Padron-McCarthy
How modern would my C have to be? My current C has it, but what's the chances I'll hit a C that doesn't have it? When was %tx added to the spec?
Southern Hospitality
+4  A: 

It's ptrdiff_t. From man stddef.h:

ptrdiff_t
              Signed integer type of the result of subtracting two pointers.

Print it with %td.

Nikolai N Fetissov
+19  A: 

b - a is a ptrdiff_t, which you can print with %td in your printf format. From the spec section 6.5.6 Additive operators:

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.

For printf and related functions, section 7.19.6 Formatted input/output functions:

t Specifies that a following d, i, o, u, x, or X conversion specifier applies to a ptrdiff_t or the corresponding unsigned integer type argument; or that a following n conversion specifier applies to a pointer to a ptrdiff_t argument.

I poked around in the spec some more, and it seems to indicate that a difference of two pointers might not even fit in a ptrdiff_t, in which case behaviour is undefined:

J.2 Undefined behavior
- The result of subtracting two pointers is not representable in an object of type ptrdiff_t (6.5.6).

Though I can't imagine any implementation where that might come up. I guess you could check PTRDIFF_MIN and PTRDIFF_MAX in <stdint.h> to be really sure.

Carl Norum
+1 For C standard quirks like a pointer difference not fitting inside of a `ptrdiff_t` (I can also think of much nastier words than "quirks").
Andrew Keeton
Yeah, it turns out that `PTRDIFF_MIN`/`PTRDIFF_MAX` might be as small as +/-65536 (according to the spec). The implementations I checked didn't do crazy stuff like that though - in practice something strange like that seems unlikely to come up.
Carl Norum
+11  A: 

The result of b - a is only defined when both a and b point to elements of the same char array. This requirement can also be interpreted as a and b pointing to bytes belonging to the same object, since every object can be re-interpreted as a char array.

Otherwise, the result is undefined. I.e. an attempt to subtract such pointers results in undefined behavior.

When the result is defined, it has ptrdiff_t type. ptrdiff_t is a typedef name and what type is hiding behind that typedef name is implementation-defined. The type is known to be signed though.

Also note, that C language does not guarantee that ptrdiff_t is large enough to hold the result of any subtraction, even if the pointers are pointing to the elements of the same array. If the pointers are too far apart for the type ptrdiff_t to accomodate the result, the behavior is undefined.

There's no specific printf format specifier for ptrdiff_t even in C99, so you'll probably be better off converting it to a sufficiently large signed integer type and use a format specifier for that type

printf("%ld\n", (long) (b - a));

Correction: C99 does have a length modifier for ptrdiff_t. The proper way to print the result in C99 would be

printf("%td\n", b - a);

Note that t is a length modifier. It can be combined with d, o, u, x or X conversion specifiers, depending on what output format you want to obtain. In C89/90 you would still have to stick with using a sufficiently large signed type.

P.S. You said that you can't imagine it failing on a 32-bit or 64-bit machine. In fact, it is very easy to imagine (or to actually make it) fail. You see the ptrdiff_t on a 32-bit machine is usually a 32-bit type. Since it is a signed type, it has only 31 bit available to represent the magnitude of the value. If you take two pointers that are farther apart (i.e. require 32 bits to represent the "distance"), the result of b - a will overflow and will be meaningless. In order to prevent this failure you'd need at least 33-bit signed ptrdiff_t on a 32-bit machine, and at least 65-bit signed ptrdiff_t on 64-bit machine. Implementations normally don't do that, they just use the "permission" from the standard to produce undefined behavior on the overflow.

AndreyT
What's "sufficiently large"? Casting to a long seems "sufficiently large" at first glance, is there a system where a pointer won't fit in a long and requires a long long? Clearly, on 32-bit systems a long is 32-bits and so is large enough, and on 64-bit systems a long is 64-bits and so is large enough. But am I really covered for all (ha!) systems?Thanks for the warning about signs, I'm not very worried about that the two pointers in question do really point into the same array, and I can't imagine that array would be larger than half the size of the address space.
Southern Hospitality
"Sufficiently large" means: analyze yor platform (pointer size, specifically) and take a look at the sizes of the various integral types on your platform. Choose a particular integral type accordingly. Normally that would be a signed integral type whose size is the same as the pointer size.
AndreyT
+2  A: 

The type of b-a is ptrdiff_t

Nemanja Trifunovic