I'm trying to get SIZE_MAX
in C89.
I thought of the following way to find SIZE_MAX
:
const size_t SIZE_MAX = -1;
Since the standard (§6.2.1.2 ANSI C) says:
When a signed integer is converted to an unsigned integer with equal or greater size, if the value of the signed integer is nonnegative, its value is unchanged. Otherwise: if the unsigned integer has greater size, the signed integer is first promoted to the signed integer corresponding to the unsigned integer; the value is converted to unsigned by adding to it one greater than the largest number that can be represented in the unsigned integer type 28
With footnote 28:
In a two's-complement representation, there is no actual change in the bit pattern except filling the high-order bits with copies of the sign bit if the unsigned integer has greater size.
This seems like this has defined behavior, but I'm not quite sure if I understand the wording of that paragraph correctly.
Note that this question is explicitly about C89, so this doesn't answer my question because the standard has different wording.
If that doesn't work, the other way I came up with is:
size_t get_size_max() {
static size_t max = 0;
if (max == 0) {
max -= 1U;
}
return max;
}
But I couldn't find anything about unsigned integer underflow in the standard, so I'm poking in the dark here.
I recommend using the macro definition as described in M.M's answer.
In some cases, you might need a similar macro, but as a numerical constant, so that you can use it in preprocessor directives like
#if VALUE > 42
...#endif
. I commented that in such cases, a helper program can be run at compile time, to compute and print a header file defining such constants.Obviously, this will not work when cross-compiling to a different architecture; in that case, the header file must be provided by some other way. (For example, the project could have a subdirectory of pre-generated headers, and a list of known architectures for each, so that the user can simply copy the header file into place.)
Creating a Makefile and associated facilities for running such programs (and only if the user did not copy the header file into place), is not difficult.
First, let's say your program consists of two source files, foo.c:
and a bar.c:
The above bar.c converts the
SIZE_MAX
preprocessor macro to a string, and prints it. If we had#define SIZE_MAX (size_t)(-1)
, it would printSIZE_MAX = "(size_t)(-1)"
.Note that bar.c includes file size_max.h, which we do not have. This is the header file we intend to generate using our helper program, size_max.c:
chux noted in a comment that
u
suffix (for sufficiently large unsigned integer type) might be necessary. If that is not what you require, I'm sure you can modify the macro generator helper to suit your needs.M.M noted in a comment that
%z
is not supported by ANSI C/ISO C90, so the above program first creates the constant using(size_t)(-1)
, then casts and prints it in theunsigned long long
format.Now, Makefiles can be written in an OS-agnostic manner, but I'm too lazy to do that here, so I shall use the values that work with GNU tools. To make it work on other systems, you only need to modify the values of
CC
, to reflect the compiler you useCFLAGS
, to reflect your preferred compiler optionsLD
, to reflect your linker, unless the same asCC
LDFLAGS
, if you need some linker flags (maybe-lm
?)RM
, to reflect the command to delete unnecessary filesFile names, if your build system requires some funky file name extension for executables
Anyway, here's the Makefile:
Note that the indentation should use tabs, not spaces, so if you copy-paste the above, run e.g.
sed -e 's|^ *|\t|' -i Makefile
to fix it.Before zipping or tarring the source tree, run
make clean
to remove any generated files from it.Note the extra
size_max.h
in the recipe prerequisites. It tellsmake
to ensure thatsize_max.h
exists before it can complete the recipe.The downside of this approach is that you cannot use
$^
in link recipes to refer to all prerequisite file names.$<
refers to the first prerequisite file name. If you use GNU make or a compatible make, you can use$(filter-out %.h, %^)
(to list all prerequisites except for header files), though.If all your binaries are built from a single source with the same name, you can replace the last two recipes with
On my system, running
outputs
and running
outputs
Note that make does not detect if you change compiler options, if you edit the Makefile or use different
CFLAGS=
orCC=
options when running make, so you do need then specify theclean
target first, to ensure you start from a clean slate with the new settings in effect.During normal editing and builds, when you don't change compilers or compiler options, there is no need to
make clean
between builds.You could use:
The behaviour of converting
-1
to unsigned integer type is defined under section C11 6.3.1.3 "Conversions - Signed and unsigned integers". C89 had an equivalent definition, numbered 3.2.1.2. In fact you quoted the ISO C90 definition 6.2.1.2 in your question (the difference between ANSI C89 and ISO C90 is that the sections are numbered differently).I would not recommend using a
const
variable, since they cannot be used in constant expressions.Note: This can't be used in C90 preprocessor arithmetic, which only works on integer constant expressions that contain no casts or words, so we can't use any
sizeof
tricks. In that case you might need a system-specific definition; there's no standard way for the preprocessor to detect a typedef.