On the larger issue of "write portable code in the first place",
Martin Wells and Craig Gullixson are correct (in my opinion) and
I will not add more than that.
On the specifics of mixing signed and unsigned...
Signed/unsigned numbers have different ranges. Why is it a big deal to
compare these two types of values? Is it because one type can store a
value that does not exist in the other? That's also a problem with
short and long ints. Anyway the solution can be simple, such as
converting the numbers into a type that accommodates both ranges.
Indeed.
I think the "big deal" is that people get confused about the
possible problems. It helps, I think, to take a step or two
back and think about the actual inputs.
Suppose that you have two variables denoted "x" and "y", which
have differing types, but which are otherwise comparable with
relational operators.
The possible range for x is X_MIN to X_MAX, and the possible
range for y is Y_MIN to Y_MAX.
If there is a common type Z, for which numbers in X_MIN to X_MAX
and Y_MIN to Y_MAX always fit within Z_MIN to Z_MAX, then the
C code:
(Z)x < (Z)y
suffices. For example, if x and y are "signed char" and "unsigned
char" respectively, and we can be reasonably sure that INT_MAX meets
or exceeds UCHAR_MAX, then a simple:
(int)x < (int)y
suffices. If x is near SCHAR_MIN, say -125, and y is a value such
as (say) 200, we just get -125 < 200, which is true.
If there is no such common type -- for instance, if the type for
x is "signed long long" and the type for y is "unsigned long long"
-- then we have a *slightly* thornier problem. In this particular
case, we must decide whether negative values of "x" are less than
all values of "y". If so:
x < 0 || (unsigned long long)x < y
will do the trick. Even if x is near LLONG_MIN, so that forcing
x to "unsigned long long" produces a number very near ULLONG_MAX,
the first test takes care of the problem.