va_arg and short

  • Thread starter Glen Herrmannsfeldt
  • Start date
G

Glen Herrmannsfeldt

Mark Gordon said:
I think that Dan's point is that 6 weeks later someone else calls that
function (or does some other change) without realising that you expect
a short.


It doesn't. The following are the same as far as ISO C is concerned.

short i = va_arg(ap,int);
short i = (short)va_arg(ap,int);

No, I meant int i=(short) va_arg(ap,int);
According to P197 of K&R2, "When any integer is converted to a signed
tpe, the value is unchanged if it can be represented in the new type and
is implementation defined otherwise".
So casting or assigned an integer to a short is not guaranteed to
enforce the range, and if the implementation documented that on
accessing the short you might get the original int value (say because it
is stored in a register and has not been truncated) then I would assume
it would still be conforming and your use of it as an array index could
then invoke undefined behaviour.

That was the question I was ask, whether the compiler would enforce the
cast. It seems, then, that it doesn't, which is the answer to the question
I was trying, not very successfully, to ask.

No, I mean something like int i=va_arg(ap,int) & 0x7fff; /* assume I want
16 bit signed short */

(snip)
If, on the other hand, you use unsigned short the result is defined by
the standard, but it is unlikely to be what you want.

IMHO you should write code to check the value yourself if you really
want a short when it is being passed as part of a varidac parameter.
Either that or accept that out of range values will invoke incorrect
behaviour (depending on your use you may not invoke UB).

Yes, that is probably the best way. I was asking if the other way would
work, and apparently it doesn't.
In that case you may well find that short and double are identical.

So it also doesn't enforce casts to (float).
It does not have to.

That is what I was asking.
IIRC when casting from double to float it has to do the cast to enforce
the correct precision, but it still does not have to do what you expect
for out of range values.

No, I was only asking about the precision question for the float/double cast
question.

No, the question was for a cast, applied to the result of the va_arg() call.
If you are assigning to a short it makes not difference.
If the value is converted to a short (either by cast or due to
assignment) and it is out of range you invoke implementation defined
behaviour which I would expect to not be what you want.

Well, I might use it in a subscript for an array dimensioned USHORT_MAX, and
cast to (unsigned short) in attempt to force the subscript to be within
range. There are other ways to do it, and it seems like this way doesn't
work. That is all that I was asking, not for a long discussion. Well, the
original question was answered a long time ago, but this new question was
not.

-- glen
 
G

Glen Herrmannsfeldt

Dan Pop said:
In <[email protected]> (e-mail address removed)
(James Kuyper) said:
Chapter and verse, please.

I was (more recently) asking about the effect of the cast on the result,
such as:

int i=(short) va_arg(ap,int);

Someone else had suggested that cast, not me.

It seems from a previous reply that the compiler isn't obliged to enforce
that cast. That i may have values outside those allowable for a short.

thanks for the discussion,

-- glen
 
K

Kai Henningsen

I wonder why va_arg could not accept a type subject to promotion and
handle them accordingly, i.e. accept the promoted type implicitly and
convert it to the original/requested type. [So, cases like the
character types and unsigned short, which might promote to signed or
unsigned int, could be handled without having to resort to integer
limit checks.]

va_arg is a macro. This is insanely hard to get right from a macro (unless
the compiler offers some builtin functions specifically to get it right),
because at the preprocessor stage you don't normally have the kind of
information you need to determine what the default promotions are.

Or, to put it another way, the default promotions are inherently
unportable.

And even if you have that information for a specific implementation, it's
not really very much fun to determine what to do from a type with only the
preprocessor. You *really* want a builtin here - or a guarantee that you
don't have to cope with unpromoted types.

For an excercise, write a macro that determines which integer type a user
defined type actually is. Your choice of how to return the result. Then
remember that va_arg also needs to cope with floats, pointers, structs ...

The preprocessor wasn't designed to cope with this sort of problem.

Kai
 
K

Kai Henningsen

When you use the va_arg macro, the type that you supply must (with some
exceptions that don't apply here) match the type of the actual argument
_after default promotions_.

Any argument of type short would be promoted to int, so using short in
the va_arg macro can never match the type of the actual argument after
default promotions.

A conforming implementation could have short == int, though, in which case
short would be just fine to use.

Once there were quite a large number of implementations for which this was
true.

Kai
 
K

Kai Henningsen

The standard(s) did add mandatory extension requirements for things like
setjmp.

How so? AFAIK, setjmp() was traditionally implemented without compiler
extensions of any kind. (It did typically use library routines written in
assembler, which is a different thing from a compiler extension.)

Kai
 
C

Christian Bau

(e-mail address removed) (Christian Bau) wrote on 02.11.03 in


A conforming implementation could have short == int, though, in which case
short would be just fine to use.

No, you can't have short == int. You can have sizeof short == sizeof
int, you can have SHORT_MAX == INT_MAX etc., but you cannot have short
== int.
Once there were quite a large number of implementations for which this was
true.

You would still have undefined behavior. A program producing undefined
behavior may produce exactly the behavior you expect, but it is still
undefined behavior.
 
D

Dan Pop

In said:
What is actually wanted is to enforce the total size limit of
SIZE_MAX. That is not necessarily the same as checking for
wraparound.

What is the difference? How else can you enforce the total size limit
of SIZE_MAX?
As usual, you haven't bothered to understand any point of view
but your own.

As usual, you're naive enough to believe that you can save your ass by
hand waving replies.

Dan
 
D

Dan Pop

I was (more recently) asking about the effect of the cast on the result,
such as:

int i=(short) va_arg(ap,int);

Isn't the effect of the cast clearly enough described by the standard?
Someone else had suggested that cast, not me.

It seems from a previous reply that the compiler isn't obliged to enforce
that cast. That i may have values outside those allowable for a short.

You will have whatever value your caller has provided. What is the merit
of losing this value via a cast and getting some *garbage* instead, if the
caller provided a value outside the range of short? And how many times
do I have to ask this question without getting a reply?

Furthermore, C99 allows the cast to raise an arbitrary signal, if the
value is out of range. Hopefully, no sane implementor would do that...

Dan
 
F

Fergus Henderson

What is the difference? How else can you enforce the total size limit
of SIZE_MAX?

For size_t x and y, you have x <= SIZE_MAX && y <= SIZE_MAX,
and x + y <= SIZE_MAX iff SIZE_MAX - x >= y,
and x * y <= SIZE_MAX iff x == 0 || SIZE_MAX / x >= y.

You can hence enforce the total size limit using functions such as
the following:

size_t plus(size_t x, size_t y) {
return (SIZE_MAX - x >= y) ? x + y ; error();
}

size_t times(size_t x, size_t y) {
return (x == 0 || SIZE_MAX / x >= y) ? x * y ; error();
}
 
D

Dan Pop

In said:
For size_t x and y, you have x <= SIZE_MAX && y <= SIZE_MAX,
and x + y <= SIZE_MAX iff SIZE_MAX - x >= y,
and x * y <= SIZE_MAX iff x == 0 || SIZE_MAX / x >= y.

You can hence enforce the total size limit using functions such as
the following:

size_t plus(size_t x, size_t y) {
return (SIZE_MAX - x >= y) ? x + y ; error();
}

size_t times(size_t x, size_t y) {
return (x == 0 || SIZE_MAX / x >= y) ? x * y ; error();
}

Thanks. Has anyone actually did it this way in *real* C code?

Dan
 
D

Dan Pop

In said:
Thanks. Has anyone actually did it this way in *real* C code?

And a related question: is calloc() *required* to fail if nmemb * size
exceeds SIZE_MAX?

Dan
 
J

James Kuyper

....
[Re: (short)va_arg(ap,int)]
Chapter and verse, please.

The relevant section is 6.3.1.3, but I overstated the problem a
little; it doesn't say "undefined". What it says is "either the result
is implementation-defined or an implementation-defined signal is
raised".

However, there's no portable way for a program to determine which
signal (if any) needs to be caught, and even if you do catch it,
there's almost nothing portably safe you can do from the signal
handler, and returning from the signal handler might itself cause
undefined behavior. Even if you merely get an implementation-defined
value, that's no better than calling rand().

Therefore, as a practical matter there's little difference between
this and undefined behavior, at least for programs intended to be
portable. I do concede, though, that it is in fact
implementation-defined, not undefined.
 
D

Dan Pop

In said:
...
[Re: (short)va_arg(ap,int)]
Chapter and verse, please.

The relevant section is 6.3.1.3, but I overstated the problem a
little; it doesn't say "undefined". What it says is "either the result
is implementation-defined or an implementation-defined signal is
raised".

However, there's no portable way for a program to determine which
signal (if any) needs to be caught, and even if you do catch it,
there's almost nothing portably safe you can do from the signal
handler, and returning from the signal handler might itself cause
undefined behavior. Even if you merely get an implementation-defined
value, that's no better than calling rand().

Therefore, as a practical matter there's little difference between
this and undefined behavior, at least for programs intended to be
portable. I do concede, though, that it is in fact
implementation-defined, not undefined.

Furthermore, there is no known conforming implementation where a signal
is raised (this is a new C99 "feature" that would break existing and
*portable* C89 code).

Dan
 
G

Glen Herrmannsfeldt

(snip of related question)
And a related question: is calloc() *required* to fail if nmemb * size
exceeds SIZE_MAX?

What do you do on those unfortunate systems where pointers can address
memory much larger than size_t?

(Remembering all the tricks from the large model x86 days. Maybe that would
be huge model, which I tried not to use.)

This is not unrelated to the fseek() and ftell() problem on file systems
that allow files greater than 2GB or 4GB, but no convenient way to address
them.

-- glen
 
G

Glen Herrmannsfeldt

Dan Pop said:
In <[email protected]> (e-mail address removed)
(James Kuyper) writes:

(snip)
(snip)

Furthermore, there is no known conforming implementation where a signal
is raised (this is a new C99 "feature" that would break existing and
*portable* C89 code).

Not at all trying to start a language war, (there are enough already), but
in PL/I returning from signal handlers is supposed to be portably safe and
well defined. (There is the assumption that the OS and hardware support it.
Machines with imprecise interrupts tended not to support it very well.)

I was disappointed that Java supplies no way to return from signal handlers.

-- glen
 
P

Peter Nilsson

Douglas A. Gwyn said:
What is actually wanted is to enforce the total size limit of
SIZE_MAX.

Recently, I've taken to writing code that assumes that the largest
safe size of an object allocation is the minimum of PTRDIFF_MAX and
SIZE_MAX.
 
D

Douglas A. Gwyn

Dan said:
Thanks. Has anyone actually did it this way in *real* C code?

Most programmers don't try to create objects so large that
they have to worry about SIZE_MAX. If you do have to worry,
use a method like what Francis showed.
 
D

Douglas A. Gwyn

It probably doesn't matter; how is a s.c. program going to
treat the resulting chunk of memory as a single object with
size exceeding SIZE_MAX?
What do you do on those unfortunate systems where pointers can address
memory much larger than size_t?

The answer to questions on this general issue is the subject
of some debate, so if you want an authoritative resolution
you need to get a DR submitted asking for clarification.
Some think that the implementation is obliged to choose a
type for size_t such that this cannot happen.
This is not unrelated to the fseek() and ftell() problem on file systems
that allow files greater than 2GB or 4GB, but no convenient way to address
them.

That's an implementation that has made some unwise choices.
You can use fgetpos/fsetpos for many such purposes, but there
is no standard way to perform arithmetic on those offset
cookies.
 
G

Glen Herrmannsfeldt

It probably doesn't matter; how is a s.c. program going to
treat the resulting chunk of memory as a single object with
size exceeding SIZE_MAX?

Well, that is a good question. Maybe 16 bit int is long past, and we
shouldn't worry about the problem. Though with 32 bit int and memories
larger than 4GB maybe we should. Consider a machine with 64 bit pointers,
but 32 bit int. You could still increment the pointer multiple times, less
than 2GB each time. Now, reasonably likely the machine would also have a
64 bit long or long long, but maybe doesn't supply the ability to increment
pointers (or subscript arrays) with them.

I notice some time ago that Java requires subscripts to be int (or castable
to int).
The answer to questions on this general issue is the subject
of some debate, so if you want an authoritative resolution
you need to get a DR submitted asking for clarification.
Some think that the implementation is obliged to choose a
type for size_t such that this cannot happen.
That's an implementation that has made some unwise choices.
You can use fgetpos/fsetpos for many such purposes, but there
is no standard way to perform arithmetic on those offset
cookies.

Those are interesting, but it seems that some still have a 32bit limit.
HP-UX seems to have non-standard fgetpos64() and fsetpos(64).

Well, the argument of fseek() and the return from ftell() were long (or even
int?) many years before 4GB of memory was affordable, real or virtual. It
looks like a mistake now, and then we have things like fseek64() and
ftell64().

Some systems have a 64 bit long, but that seems relatively rare these days.

-- glen
 
N

Niklas Matthies

Thanks. Has anyone actually did it this way in *real* C code?

Yes, though I used type-generic macros.
Excerpt:

/*
* T is assumed to be an integer type with minimum value T_MIN and
* maximum value T_MAX. Yields +1 or -1 if the multiplication of
* a and b in T would result in an overflow or underflow,
* respectively, otherwise 0.
*
* Preconditions: (1) a and b are integer values
* (2) T is an integer type
* (3) T_MIN and T_MAX are values of integer type
* (4) T_MIN and T_MAX are valid limits of type T
* (5) T can represent a and b
*
* Does not compile if either T_MIN, T_MAX, a or b is not a numeric
* value or if T is not a numeric type.
*/

#define UT__integer_prd_ovf(T, T_MIN, T_MAX, a, b) ( \
(T)(a) > 0 \
? (T)(b) > 0 \
? (T)(T_MAX) / (T)(b) < (T)(a) ? +1 : 0 \
: (T)(T_MIN) / (T)(a) > (T)(b) ? -1 : 0 \
: (T)(b) > 0 \
? (T)(T_MIN) / (T)(b) > (T)(a) ? -1 : 0 \
: (T)(b) < 0 \
? ( (T)(T_MAX) + (T)(T_MIN) > 0 \
? ( (T)(T_MAX) / -(T)(b) < -(T)(a)) \
: (-((-(T)(T_MAX)) / (T)(b)) > (T)(a)) \
) ? +1 : 0 \
: 0 \
)

#define UT_integer_prd_ovf(T, T_MIN, T_MAX, a, b) ( \
UT_PRECONDITION("a is an integer value", \
UT_is_integer_value(a)), \
UT_PRECONDITION("b is an integer value", \
UT_is_integer_value(b)), \
UT_PRECONDITION("T is an integer type", \
UT_is_integer_type(T)), \
UT_PRECONDITION("T_MIN is an integer value", \
UT_is_integer_value(T_MIN)), \
UT_PRECONDITION("T_MAX is an integer value", \
UT_is_integer_value(T_MAX)), \
UT_PRECONDITION("T_MIN and T_MAX are valid limits of type T", \
UT__valid_integer_type_limits(T, T_MIN, T_MAX)), \
UT_PRECONDITION("T can represent a", \
UT__convertible_to_integer_type(a, T, T_MIN, T_MAX)), \
UT_PRECONDITION("T can represent b", \
UT__convertible_to_integer_type(b, T, T_MIN, T_MAX)), \
UT__integer_prd_ovf(T, T_MIN, T_MAX, a, b) \
)

[...]

#define UT_size_prd_ovf(a, b) UT_integer_prd_ovf(size_t, 0, SIZE_MAX, a, b)

There were similar macros for all common integer types and operations,
for saturation arithmetics and so on.

-- Niklas Matthies
 

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

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top