5 ms·
I think the opposite - signed types are a big problem in statically typed languages and the cause of countless bugs I've had to deal with (in other people's cod
by pslam 13y ago
I think the opposite - signed types are a big problem in statically typed languages and the cause of countless bugs I've had to deal with (in other people's code) for most of my career.
I think most languages would benefit from unsigned types being the default, and arithmetic overflow being a hard error unless otherwise decorated. Signedness and lenient overflow promote laziness. Array indexes don't make sense as signed, yet most people prefer to iterate arrays with a signed type, e.g most commonly:
for (int i = 0; i < 10; ++i) buf[i] = 0;
Far too many people rely on signed integer overflow working as you expect the underlying machine to handle it - but that's not what the C spec says and not how a compiler handles it either.
There are countless security issues I've had to fix related to signed types in what is supposed to be secure code. These would not have occurred if the author was forced to use an unsigned type, and had to consider the extreme limits of the values it can take on. Subtle things such as the addressable limit of memory being naturally unsigned, but buffer sizes being passed as signed, can cause easy exploits, and are stupidly commonplace.
- oleganza 13y agobuf[-1] is equally incorrect as buf[4294967295], so int being signed or unsigned does not make any difference to the correctness of your loop.
- dllthomas 13y agoNot so. buf[-1] will step on mapped memory that is probably something else important, and you won't find out about it until later, which is hard to debug and more likely disastrous (if there's room for disaster in the use of the software). buf[4294967295] will try to use memory that is almost certainly unmapped and unavailable, and will segfault immediately - right on the line that caused the problem - and won't take any potentially disastrous actions.
- pslam 13y agobuf[4294967295] is perfectly valid and expected on platforms where size_t is 64 bits and int is 32 bits. buf[-1] is almost always a bug, and would be useful to default (with overrides) as such.
- dllthomas 13y agoif buf[4295967295] is valid, then you should almost certainly be indexing with something bigger than 32 bits, and the max of your index is again likely to be invalid (and, as I've mentioned elsewhere, more usefully invalid than -1).
- dllthomas 13y agoOn reflection, using something larger doesn't help, since if sizeof(void*) == sizeof(i) then a[(__typeof(i))-1] will almost certainly refer to the same address whether i is signed or unsigned.
- cgore 13y agoActually, array indices do make sense as negative. For example, you might want an array going from a[-500] to a[700], without having to adjust the index all the time. It is much nicer if the array does it for you. Fortran lets you change the starting index if I remember correctly. Most commonly you'll actually want to go positive, and have an array going from a[1970] to a[2013] or something similar.
- frenchy 13y agoPerhaps, though from a language design standpoint I think that adds unnecessary complexity. Either way, we're talking about C here, and in that context signed integers don't make sense as array indexes.
- dllthomas 13y agoThe fact that we're talking about C doesn't really make a difference, if you genuinely desire negative indices: int array[FOO]; int *shifted_array = array + FOO/2;
- cgore 13y agoThat's actually a nice trick. I just hope the compiler doesn't complain about it. I'll have to try that sometime. EDIT: just tried it. The following works: #include <stdio.h> int main(void) { int array[100]; int *shifted_array = array - 1980; for (int i = 0; i < 100; i++) array[i] = i; for (int j = 1980; j < 2014; j++) printf("shifted_array[%d] = %d\n", j, shifted_array[j]); return 0; }
- dllthomas 13y agoYeah, there's a couple cases where this kind if thing cleans things up; use it with caution though. More amusing, but less useful outside the IOCCC: The C subscripting operator performs an basically syntactic transformation, turning a[i] into (*(a + i)). This means that, counter-intuitively (if you're thinking of [] as a lookup) the construct i[a] works too because plus is commutative.
- elbee 13y agoOn the other hand unsigned types are a huge pain if you want to iterate through an array backwards because you have to use subtraction. A lot of people end up with something like this: unsigned int i = strlen(s) - 1; for (; i >= 0; --i) { // BUGBUG if (s[i] == '.') { break; } } (Yes, you can make it work, but it is very error-prone when people try).
- pslam 13y agoSimple transformation: for (unsigned i = strlen(s); i > 0; --i) { if (s[i - 1] == '.') break; } Easy to see that s[i-1] does not underflow the array due to the loop invariant i>0. It's usually easy to convert signed iteration code to unsigned and when I see this, I can tell the author spent the time to consider what happens at the limits of their inputs.
- beagle3 13y agogcc has been complaining about that one for the last 15 years or so, saying "condition is always true i >= 0". Some errors are more subtle and not flagged by compilers, but many are.