Subtracting pointers: Which type to use to store the result?

S

Spiro Trikaliotis

Hello,

I have a question regarding subtracting a pointer from another one.
Assume I have two pointers p1 and p2, which both point to a memory area
obtained with malloc(). Assume p1 = p2 + some value c.

Now, I want to obtain the difference between the two, that is, the value
c. Which type must I use for c?

I searched around and did not find a definitive answer (not even in the
FAQ). Anyway, http://c-faq.com/malloc/realloc.html has a code sample
which uses:

int tmpoffset;

p = malloc(10);
strcpy(p, "Hello,");/* p is a string */
p2 = strchr(p, ',');/* p2 points into that string */

tmpoffset = p2 - p;


Thus, this examples assumes that an int should be sufficient (or that
example is broken).

Unfortunately, my compiler complains on the "tmpoffset = p2 -p;" line:

file.c(745): error C4242: '=' : conversion from '__int64' to 'int', possible loss of data


(Yes, this is a 64 bit MS compiler, but that should not matter.)

Of course, I could just use __int64 type, but this does not seem the
best portable way for me. I could cast p2-p, as I know the difference
can't be that big not to fit into an int, but this is no good solution
either. Additionally, I doubt size_t to be a good candidate here,
either.

So, my question is: Thinking of portability, which type should I use
here? Or is the MS compiler wrong?

Thanks,
Spiro.
 
P

pemo

Spiro said:
Hello,

I have a question regarding subtracting a pointer from another one.
Assume I have two pointers p1 and p2, which both point to a memory
area
obtained with malloc(). Assume p1 = p2 + some value c.

Now, I want to obtain the difference between the two, that is, the
value
c. Which type must I use for c?

I searched around and did not find a definitive answer (not even in
the
FAQ). Anyway, http://c-faq.com/malloc/realloc.html has a code sample
which uses:

int tmpoffset;

p = malloc(10);
strcpy(p, "Hello,");/* p is a string */
p2 = strchr(p, ',');/* p2 points into that string */

tmpoffset = p2 - p;


Thus, this examples assumes that an int should be sufficient (or that
example is broken).

Unfortunately, my compiler complains on the "tmpoffset = p2 -p;" line:

file.c(745): error C4242: '=' : conversion from '__int64' to 'int',
possible loss of data


(Yes, this is a 64 bit MS compiler, but that should not matter.)

Of course, I could just use __int64 type, but this does not seem the
best portable way for me. I could cast p2-p, as I know the difference
can't be that big not to fit into an int, but this is no good solution
either. Additionally, I doubt size_t to be a good candidate here,
either.

So, my question is: Thinking of portability, which type should I use
here? Or is the MS compiler wrong?

Thanks,
Spiro.


ptrdiff_t
 
A

Alex Fraser

Spiro Trikaliotis said:
I have a question regarding subtracting a pointer from another one.
Assume I have two pointers p1 and p2, which both point to a memory area
obtained with malloc(). Assume p1 = p2 + some value c.

Now, I want to obtain the difference between the two, that is, the value
c. Which type must I use for c?

The result of (p1 - p2) has type ptrdiff_t, but size_t must be able to
represent this value, since the situation guarantees it is non-negative and
less than or equal to the size (a value of type size_t) passed to malloc().

In my experience, the guarantee above is normally the case, and often the
value is compared with a size_t value, so size_t makes sense to me.

Alex
 
S

stathis gotsis

Vladimir S. Oka said:
And remember to #include <stddef.h>

Also remember that there is no guarantee that all pointer differences are
representable as a ptrdiff_t.
 
F

Fred Kleinschmidt

Alex Fraser said:
The result of (p1 - p2) has type ptrdiff_t, but size_t must be able to
represent this value, since the situation guarantees it is non-negative
and
less than or equal to the size (a value of type size_t) passed to
malloc().

No, there is no guarantee that (p1-p2) is non-negative.
The OP stated " Assume p1 = p2 + some value c"
He did NOT say that c was positive; as far as we know, it could very well be
negative.
 
J

Jordan Abel

What would you use then?

Still ptrdiff_t - if it's not representable it'll mess up before you get
to it, since the type of the expression p2-p1 (given that both p1 and p2
are pointers to the same type, of course) is ptrdiff_t.
 
P

pete

Richard said:
What would you use then?

I believe that in a conforming program
which doesn't excede minimum environmental limits,
you're OK with ptrdiff_t, in C99.

I wrote some heapsort and quicksort functions,
that had a part that calculated a pointer difference.
I felt like making them more robust,
so I rewrote them with pointers and size_t offsets.

/* pointier version */
void q_sort(void *base, size_t nmemb, size_t size,
int (*compar)(const void*, const void*))
{
unsigned char *left, *middle, *last, *right;
size_t nmemb_right;
unsigned char *p1, *p2, *end, swap;

left = base;
while (nmemb-- > 1) {
right = left + nmemb * size;
last = middle = left;
do {
middle += size;
if (compar(left, middle) > 0) {
last += size;
BYTE_SWAP(middle, last);
}
} while (middle != right);
BYTE_SWAP(left, last);
nmemb = (last - left) / size;
nmemb_right = (right - last) / size;
if (nmemb_right > nmemb) {
q_sort(left, nmemb, size, compar);
left = last + size;
nmemb = nmemb_right;
} else {
q_sort(last + size, nmemb_right, size, compar);
}
}
}

/* size_t offset version */
void q_sort(void *base, size_t nmemb, size_t size,
int (*compar)(const void*, const void*))
{
unsigned char *left;
size_t nmemb_right, middle, last, right;
unsigned char *p1, *p2, *end, swap;

left = base;
while (nmemb-- > 1) {
right = nmemb * size;
last = middle = 0;
do {
middle += size;
if (compar(left, left + middle) > 0) {
last += size;
BYTE_SWAP(left + middle, left + last);
}
} while (middle != right);
BYTE_SWAP(left, left + last);
nmemb = last / size;
nmemb_right = (right - last) / size;
if (nmemb_right > nmemb) {
q_sort(left, nmemb, size, compar);
left += last + size;
nmemb = nmemb_right;
} else {
q_sort(left + last + size, nmemb_right, size, compar);
}
}
}

#define BYTE_SWAP(A, B) \
{ \
p1 = (A); \
p2 = (B); \
end = p2 + size; \
do { \
swap = *p1; \
*p1++ = *p2; \
*p2++ = swap; \
} while (p2 != end); \
}
 
S

stathis gotsis

pete said:
I believe that in a conforming program
which doesn't excede minimum environmental limits,
you're OK with ptrdiff_t, in C99.

I do not understand what you mean by that restriction, but on my system
where size_t and ptrdiff_t are the same size while the former is unsigned
and the latter signed, it is clear that not all pointer differences can be
represented in ptrdiff_t. I think my system is C99 compliant at that point.
 
R

Richard Harnden

Fred said:
No, there is no guarantee that (p1-p2) is non-negative.

I think that that would be a logical error: it's like subtracting two
dates - if you get a negative result, the you know that something's
wrong since nothing can ever happen in a negative time. It's the same
with pointers. If (p1-p2) is negative, you should've said (p2-p1).
 
B

Ben Pfaff

Richard Harnden said:
I think that that would be a logical error: it's like subtracting two
dates - if you get a negative result, the you know that something's
wrong since nothing can ever happen in a negative time. It's the same
with pointers. If (p1-p2) is negative, you should've said (p2-p1).

I think you have an insufficiently general viewpoint. If you
subtract date B from date A and get a negative result, you know
that B is later than A. Similarly, if p1 - p2 is negative, then
p1 < p2.
 
F

Fred Kleinschmidt

Richard Harnden said:
I think that that would be a logical error: it's like subtracting two
dates - if you get a negative result, the you know that something's wrong
since nothing can ever happen in a negative time. It's the same with
pointers. If (p1-p2) is negative, you should've said (p2-p1).
Well, suppose I use strchr() to search for an 'x' and then again for a 'y'.
To find out which one came first, I subtract the pointers. The result could
be either positive or negative, yet there is no reason I "should have"
known which one would give me the positive value.
 
P

pete

stathis said:
I do not understand what you mean by that restriction,

N869
4. Conformance
[#5] A strictly conforming program shall use only those
features of the language and library specified in this
International Standard.2) It shall not produce output
dependent on any unspecified, undefined, or implementation-
defined behavior, and shall not exceed any minimum
implementation limit.

5.2.4.1 Translation limits
[#1] The implementation shall be able to translate and
execute at least one program that contains at least one
instance of every one of the following limits:

-- 65535 bytes in an object (in a hosted environment only)
 
A

Andrey Tarasevich

Richard said:
What would you use then?
...

One should still use 'ptrdiff_t' in general case. The above statement, while
true, doesn't really work as an argument against using 'ptrdiff_t'. The
important detail here is that if the pointers are distanced further apart then
the range of 'ptrdiff_t', then the very attempt to subtract one pointer from
another _already_ causes the undefined behavior. Note, it is not the attempt to
store "the overly large number" in the user-specified receiving object of type
'ptrdiff_t' that causes UB, but it is the very subtraction itself. The UB
happens "internally".

In other words, if at certain point the pointers being subtracted happen to be
too far apart, there's no way to avoid UB, regardless of which type is used to
store the result.

There's a popular incorrect opinion that using 'size_t' is guaranteed to work
without UB in cases when the result of the subtraction is positive.
Unfortunately, this is not the case. The subtraction itself initially produces a
value of 'ptrdiff_t', leading to the immediate UB in out-of-range situations.
 
K

Keith Thompson

stathis gotsis said:
I do not understand what you mean by that restriction, but on my system
where size_t and ptrdiff_t are the same size while the former is unsigned
and the latter signed, it is clear that not all pointer differences can be
represented in ptrdiff_t. I think my system is C99 compliant at that point.

That's true only if the implementation actually supports objects whose
size exceeds SIZE_MAX/2. If size_t is 32 bits, you won't run into
problems with ptrdiff_t until you have objects of at least 2
gigabytes. If size_t is 64 bits, I don't think that much memory has
ever been manufactured.
 
K

Keith Thompson

Richard Harnden said:
I think that that would be a logical error: it's like subtracting two
dates - if you get a negative result, the you know that something's
wrong since nothing can ever happen in a negative time. It's the same
with pointers. If (p1-p2) is negative, you should've said (p2-p1).

Not at all.

Given:
char foo[100];
char *p1 = &(foo[20]);
char *p2 = &(foo[10]);
we have:
p1 - p2 == -10
It's entirely correct and consistent, and it's why ptrdiff_t is
required to be a signed type.
 
A

Alex Fraser

Fred Kleinschmidt said:
No, there is no guarantee that (p1-p2) is non-negative.
The OP stated " Assume p1 = p2 + some value c"
He did NOT say that c was positive; as far as we know, it could very well
be negative.

Yes, I assumed that c was non-negative. Even though you've pointed out this
assumption, I still think it was the OP's intention, due to the phrasing.
But that's no excuse.

Alex
 
C

Christian Bau

Keith Thompson said:
That's true only if the implementation actually supports objects whose
size exceeds SIZE_MAX/2. If size_t is 32 bits, you won't run into
problems with ptrdiff_t until you have objects of at least 2
gigabytes. If size_t is 64 bits, I don't think that much memory has
ever been manufactured.

Anyway, the result of taking a pointer difference _has_ type ptrdiff_t,
so if that isn't enough, then you are out of luck, and there is nothing
you can do. Storing into a type other then ptrdiff_t cannot possibly
help, because undefined behavior happened earlier.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top