Macro argument bounds checking?

J

Jim Cook

We have a macro which takes various index constants as an argument and
offsets into an array. The macro can be an Lvalue or Rvalue. The index
is not zero based. I would like a compile time error displayed if the
index is out of bounds. Is there a way to do this well?

I read through the FAQ that I could find mentioned, and did not see this
sort of question in the preprocessor section anywhere.

What I came up with is below. It does in fact generate errors using my
16-bit and 32-bit compilers, but it feels like I'm relying a little bit
on the behavior of the MS compiler and would like to know a portable
proper C method.

Thank you for any time spent on this.


The code and run-time clip are below. The valid index range is from 20
to 27 inclusive. Any other value should produce an error. Testing for
non-integer is not required.


// #define argument range testing
#include "limits.h"

volatile unsigned short usArray[8];
volatile unsigned short usI;
volatile unsigned char ucArray[8];
volatile unsigned char ucI;

#define usAccess(r) \
(usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])
#define ucAccess(r) \
(ucArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])

void main(void)
{
usAccess(19) = 19; /* bad */
usAccess(20) = 20;
usAccess(27) = 27;
usAccess(28) = 28; /* bad */
usI = usAccess(19); /* bad */
usI = usAccess(20);
usI = usAccess(27);
usI = usAccess(28); /* bad */
ucAccess(19) = 19; /* bad */
ucAccess(20) = 20;
ucAccess(27) = 27;
ucAccess(28) = 28; /* bad */
ucI = ucAccess(19); /* bad */
ucI = ucAccess(20);
ucI = ucAccess(27);
ucI = ucAccess(28); /* bad */
}
#if 0
------------------ Output ------------------
C:\TEMP\>cl /c /W3 test.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

test.c
test.c(16) : warning C4307: '*' : integral constant overflow
test.c(19) : warning C4307: '*' : integral constant overflow
test.c(20) : warning C4307: '*' : integral constant overflow
test.c(23) : warning C4307: '*' : integral constant overflow
test.c(24) : warning C4307: '*' : integral constant overflow
test.c(27) : warning C4307: '*' : integral constant overflow
test.c(28) : warning C4307: '*' : integral constant overflow
test.c(31) : warning C4307: '*' : integral constant overflow
#endif
 
I

Ivan Vecerina

| We have a macro which takes various index constants as an argument and
| offsets into an array. The macro can be an Lvalue or Rvalue. The index
| is not zero based. I would like a compile time error displayed if the
| index is out of bounds. Is there a way to do this well?
....
| volatile unsigned short usArray[8];
| volatile unsigned short usI;
....
| #define usAccess(r) \
| (usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])

Here is something that will fail more reliably, and should
work I think (though it's at the limits of the standard):

volatile unsigned short usArray[8];

#define usAccess(r) \
( *((unsigned short(*)[20<=r&&r<=27])usArray)[r-20] )

int main()
{
usAccess(20) = 20;
usAccess(21) = 21;
usAccess(27) = 27;
usAccess(19) = 19; /* bad */
usAccess(28) = 28; /* bad */
}

The idea is that the conditional expression is used
to define the size of an array ( 0 or 1 ).
The following type expression defines a pointer
to that array:
(unsigned short(*)[20<=r&&r<=27])

A pointer of that type is then used to perform offset
calculations. And this will fail/be illegal if the array
size is zero (some compilers may even complain before).

A key feature of this approach is that it will fail
to compile if the parameter of usAccess is not a compile-
time constant (while the macro you posted would just
lead to undefined behavior...).


This is really ugly though...
(I'd rather use some C++-based technique than this...)


Regards,
 
T

The Real OS/2 Guy

We have a macro which takes various index constants as an argument and
offsets into an array. The macro can be an Lvalue or Rvalue. The index
is not zero based. I would like a compile time error displayed if the
index is out of bounds. Is there a way to do this well?

I read through the FAQ that I could find mentioned, and did not see this
sort of question in the preprocessor section anywhere.

What I came up with is below. It does in fact generate errors using my
16-bit and 32-bit compilers, but it feels like I'm relying a little bit
on the behavior of the MS compiler and would like to know a portable
proper C method.

Thank you for any time spent on this.


The code and run-time clip are below. The valid index range is from 20
to 27 inclusive. Any other value should produce an error. Testing for
non-integer is not required.


// #define argument range testing
#include "limits.h"

volatile unsigned short usArray[8];
volatile unsigned short usI;
volatile unsigned char ucArray[8];
volatile unsigned char ucI;

#define usAccess(r) \
(usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])
#define ucAccess(r) \
(ucArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])

Both macros are wrong!
Please name a number that can be in r that is both, <= 20 AND >= 27 as
required to get TRUE from the ?: operator.
The numbers 21 trough 26 in r will fail on && because the left hand
side of && will tell FALSE and the right hand side will never been
tested.
The number 20 (like all lower numbers) in r will deliver 0 because it
is truly lower than 27.
The numer 27 (like all numbers greater 20) will fail because it is >
than 20.

May be you means || but even than your limits are wrong.
------------------ Output ------------------
C:\TEMP\>cl /c /W3 test.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

test.c
test.c(16) : warning C4307: '*' : integral constant overflow
test.c(19) : warning C4307: '*' : integral constant overflow
test.c(20) : warning C4307: '*' : integral constant overflow
test.c(23) : warning C4307: '*' : integral constant overflow
test.c(24) : warning C4307: '*' : integral constant overflow
test.c(27) : warning C4307: '*' : integral constant overflow
test.c(28) : warning C4307: '*' : integral constant overflow
test.c(31) : warning C4307: '*' : integral constant overflow
#endif
2*INT_MAX is integral constant overflow!
 
I

Irrwahn Grausewitz

The Real OS/2 Guy said:
We have a macro which takes various index constants as an argument and
offsets into an array. The macro can be an Lvalue or Rvalue. The index
is not zero based. I would like a compile time error displayed if the
index is out of bounds. Is there a way to do this well?

I read through the FAQ that I could find mentioned, and did not see this
sort of question in the preprocessor section anywhere.

What I came up with is below. It does in fact generate errors using my
16-bit and 32-bit compilers, but it feels like I'm relying a little bit
on the behavior of the MS compiler and would like to know a portable
proper C method.

Thank you for any time spent on this.


The code and run-time clip are below. The valid index range is from 20
to 27 inclusive. Any other value should produce an error. Testing for
non-integer is not required.


// #define argument range testing
#include "limits.h"

volatile unsigned short usArray[8];
volatile unsigned short usI;
volatile unsigned char ucArray[8];
volatile unsigned char ucI;

#define usAccess(r) \
(usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)]) ^^^^^
#define ucAccess(r) \
(ucArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])
^^^^^
Both macros are wrong!
Please name a number that can be in r that is both, <= 20 AND >= 27 as
^^^^^^
Once again, you failed to read carefully.
required to get TRUE from the ?: operator.
The numbers 21 trough 26 in r will fail on && because the left hand
side of && will tell FALSE and the right hand side will never been
tested.
This is plain wrong.

20 <= 20 && 20 <= 27 /* TRUE */
20 <= 21 && 21 <= 27 /* TRUE */
20 <= 22 && 22 <= 27 /* TRUE */
20 <= 23 && 23 <= 27 /* TRUE */
20 <= 24 && 24 <= 27 /* TRUE */
20 <= 25 && 25 <= 27 /* TRUE */
20 <= 26 && 26 <= 27 /* TRUE */
20 <= 27 && 27 <= 27 /* TRUE */

Quod erat demonstrandum.

<SNIP>

Irrwahn
 
K

Kevin Easton

Jim Cook said:
We have a macro which takes various index constants as an argument and
offsets into an array. The macro can be an Lvalue or Rvalue. The index
is not zero based. I would like a compile time error displayed if the
index is out of bounds. Is there a way to do this well?

I read through the FAQ that I could find mentioned, and did not see this
sort of question in the preprocessor section anywhere.

What I came up with is below. It does in fact generate errors using my
16-bit and 32-bit compilers, but it feels like I'm relying a little bit
on the behavior of the MS compiler and would like to know a portable
proper C method.

Thank you for any time spent on this.


The code and run-time clip are below. The valid index range is from 20
to 27 inclusive. Any other value should produce an error. Testing for
non-integer is not required.


// #define argument range testing
#include "limits.h"

volatile unsigned short usArray[8];
volatile unsigned short usI;
volatile unsigned char ucArray[8];
volatile unsigned char ucI;

#define usAccess(r) \
(usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])
#define ucAccess(r) \
(ucArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])

Try:

#define usAccess(n) \
(*((void)(sizeof(int[((n) < 0 || (n) > 10) ? -1 : 1])), &usArray[n]))

The idea being to force a compile-time error by attempting to take the
sizeof an array with negative size. (I can't recall if conditional
operators are prohibited in compile-time constants - if so, just
multiple the result of the || operator by -2 and add 1). Methods based
on arrays of zero size won't error on gcc in non-pedantic mode, and
possibly other compilers.

- Kevin.
 
C

Capstar

The said:
#define usAccess(r) \
(usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])
#define ucAccess(r) \
(ucArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])


Both macros are wrong!
Please name a number that can be in r that is both, <= 20 AND >= 27 as
required to get TRUE from the ?: operator.

But that's not what the macro states. It states:

(20<=r && r<=27 ? 0 : INT_MAX)

which is the same as:

(r>=20 && r<=27 ? 0 : INT_MAX)

So in my opinion that means r must be bigger or equal than 20 AND
smaller or equal than 27, which is not impossible if you ask me.

Mark
 
J

Jim Cook

Here is something that will fail more reliably, and should
work I think (though it's at the limits of the standard):

volatile unsigned short usArray[8];

#define usAccess(r) \
( *((unsigned short(*)[20<=r&&r<=27])usArray)[r-20] )

Thank you. This is not only a perfect solution, but teaches me something
as well. I see also that Kevin gave essentially the same answer.

I would also like to thank Irrwahn and Mark for replying to the person
who did not read the question very carefully.
 
D

Dave Thompson

| We have a macro which takes various index constants as an argument and
| offsets into an array. The macro can be an Lvalue or Rvalue. The index
| is not zero based. I would like a compile time error displayed if the
| index is out of bounds. Is there a way to do this well?
...
| volatile unsigned short usArray[8];
| volatile unsigned short usI;
...
| #define usAccess(r) \
| (usArray[r-20 + 2*(20<=r && r<=27 ? 0 : INT_MAX)])

Here is something that will fail more reliably, and should
work I think (though it's at the limits of the standard):

volatile unsigned short usArray[8];

#define usAccess(r) \
( *((unsigned short(*)[20<=r&&r<=27])usArray)[r-20] )
The idea is that the conditional expression is used
to define the size of an array ( 0 or 1 ).
The following type expression defines a pointer
to that array:
(unsigned short(*)[20<=r&&r<=27])

A pointer of that type is then used to perform offset
calculations. And this will fail/be illegal if the array
size is zero (some compilers may even complain before).
It is a constraint violation (if constant, see below) and must be
diagnosed in a conforming compiler, but it need not fail to compile.
At least gcc supports zero-bound arrays as an extension, and by
default (if you don't ask for -pedantic[-errors]) silently. Using -1
instead of 0, as Kevin Easton does elsethread, will fix that part; it
is possible (though IMO not that useful) to define consistent
semantics for zero bound, but not for negative bound.

Also, for in-range values this effectively changes (reduces) the type
of the lvalue used to unsigned short [1], so any access other than to
(20) is arguably technically illegal -- is the "array object" in 6.5.6
the designated one or the underlying one? -- plus the nominal
dereference violates 6.5.p7. It is exceedingly (vanishingly?) rare
for a C implementation to enforce these as long as the actual access
to the actual object is valid, and in fact usually as long as the
actual access is just to usable memory; but it legally could. To be
absolutely safe, and also clear, I would use [valid? actualbound: -1].
And yes, ?: is permitted in constant expressions; assignments are not,
and fetches (thus, any objects) are not in most cases; neither is
comma operator, perhaps on grounds that it was envisioned (only) to
allow side-effects like assignment, but that is an easy extension.

Plus an implementation "may accept other forms of constant
expressions" (6.6p10) although it would be pretty perverse to accept
things that are not in fact compile-time constant; I suspect this was
intended to allow things like sin(), and maybe (pure) functions for
which a definition is available and can be evaluated at compile time,
even though general function calls are prohibited. It could also be
used in C90 to allow not-really-constant aggregate local initializers;
C99 allows this directly.
A key feature of this approach is that it will fail
to compile if the parameter of usAccess is not a compile-
time constant (while the macro you posted would just
lead to undefined behavior...).
In C90 it must be diagnosed if not constant (see above). In C99 it
(quietly!) becomes a VLA and if the bound evaluates to <= 0 it is
AFAICT only UB (6.2.5p20) and need not be diagnosed.

Bitfield sizes and enumerator explicit values must still be integer
constant expressions, and the former must be nonnegative and with a
name strictly positive but <= an implementation defined limit, in all
cases as constraint violations which must be diagnosed. And even gcc,
at least as of 2.95.2, hasn't extended these. Also subscripts in a
designated initializer, but only in C99; and case labels and #[el]if
expressions, but there's no (standard) way to use those in an
expression and thus the desired macro.

- David.Thompson1 at worldnet.att.net
 

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,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top