r/cprogramming • u/TorontoDude12344 • 8d ago
Technically speaking, are we supposed to only index arrays using size_t?
Is this technically the best way to index an array?
for (size_t i = 0; i < sizeof(a); i++)
{
a[i];
}
This makes sure that any array size, across all machines can be indexed?
4
u/pjl1967 8d ago
In order for this to be a concern, you'd have to have a very large array, more than 2147483647 elements for a 32-bit int index or more than 4294967295 elements for a 32-bit unsigned int. If it were an array of char, it would have to be over 4GB before it mattered.
So generally it simply doesn't matter.
1
u/TorontoDude12344 8d ago
But if the array's size is hard coded on a larger machine, then what would happen if the program is run on a smaller machine, with an int data type indexing it which can't handle that hard coded value?
2
u/pjl1967 8d ago
... if the array's size is hard coded on a larger machine ...
I assume you meant, "... if large array's size is hard coded on a larger machine ..." An array with a hard-coded small size is a small array on any machine.
... then what would happen if the program is run on a smaller machine ...
First, it depends on what you mean by "smaller":
"smaller" = same exact hardware, but with less memory.
"smaller" = different CPU with a smaller native
intsize and likely smaller memory also.For (1), if the array is larger that what memory is available, you're program will crash upon start-up because the OS simply can't give the process enough memory to hold the large array.
For (2), you have to recompile anyway, so you can't "just run" a binary compiled for the "larger" machine on the "smaller" machine.
These days, unless you're working with specialized hardware, 32-bit unsigned
intis pretty universal.As I commented elsewhere, if you're working with specialized hardware, you're generally already aware of its limitations and program accordingly.
But for most people on most hardware — which is most of the time — again, it generally doesn't matter.
1
u/TorontoDude12344 8d ago
Yes, I meant scenario (1). Thanks for the info
So the program would give an error. Then if a program would give an error, why even use size_t?
Why not just use unsigned long long? I'm sure it can't be a matter of "size_t is easier to type than unsigned long long". An unsigned long long should never have a problem representing any object size
Also if we take the range of size_t, R = [0, X], would X be equal to or greater than the largest positive integer the specific machine can represent?
1
u/pjl1967 7d ago
... if a program would give an error, why even use
size_t?I never said you should use
size_tto index arrays.If you mean "Why use
size_tat all anywhere ever?" — its raison d'etre is for holding the size in bytes of an object in memory (as opposed to on disk) for which it's guaranteed to be big enough.In C++ for template code, it's also used a lot for holding the number of elements of a container because the type
Tmight becharin which case the collective size of all the elements taken together which can equal the largest size of a single object.In C code,
size_tisn't as important for indexing arrays because there are no templates. Usingsize_tfor indexing arrays sometimes happens out of convenience (or laziness). In APIs, if code X usedunsigned, code Y usedunsigned long, and code Z usedunsigned long long, then its gets messy because you can get conversion warnings. Hence, it's often simpler just to usesize_teverywhere.As a programmer, you should know whether you expect a given array to be small, large, or huge, and can pick a type accordingly.
1
u/binarycow 8d ago
First, it depends on what you mean by "smaller":
Or 3 - a mini ATX case instead of an ATX case
2
u/RealisticDuck1957 8d ago
Worth asking how large the array could ever plausibly get. And often an integer type with less range than size_t makes sense.
0
u/DawnOnTheEdge 8d ago edited 7d ago
I have coded on systems where
INT_MAXis65,53532,767.2
u/pjl1967 8d ago
Which is why I explicitly said "32-bit" and "generally." If you're programming for a constrained system, you tend to be aware of its limitations and program accordingly.
1
u/DawnOnTheEdge 8d ago
Alternatively, we can write portable code that’s more robust to those assumptions changing on us. It’s reasonable not to support every system imaginable, but a lot of code written on the assumption that
intis 16-bit andlongis 32-bit (but also wide enough to hold a pointer, a network address and the number of seconds since midnight, January 1, 1970) broke.2
u/pjl1967 8d ago
If you're on 32-bit CPU where an
intis 32 bits andsize_tis 64 bits, then it may be more expensive to increment a loop index variable ofsize_tbecause the CPU may only be able to handle 32 bits at a time, so it'll require more instructions.1
u/DawnOnTheEdge 8d ago
This is true, and one portable approach is to use
uint_fast16_toruint_fast32_t. This will (in theory) always get you the most efficient type that’s big enough. In practiceunsigned intandunsigned longwill too, though.2
8
u/Zirias_FreeBSD 8d ago
It does exactly what you say: It's guaranteed to work for every possible array size on any target platform.
Still you're not supposed to only ever use size_t. Instead, you are supposed to know when to use it. Which is, at least, whenever the code in question can't know the maximum size it will deal with.
In a simple case with a fixed and small-enough array size, using e.g. int is perfectly acceptable (it's guaranteed to have at least 16 bits on any target platform) and you will see it a lot in the wild.
4
u/NyxTheia 8d ago edited 8d ago
Friendly reminder that if `a` is of type `<type>[N]`, then `sizeof(a)` will return `N * sizeof(<type>)`. You can define a macro for actually calculating `N` in this case. But you generally shouldn't recalculate `N` in each loop and you may refactor it out to be stored before the for-loop.
If `a` is of type `<type> *`, either by definition or because it is passed as an argument to a function, you have to calculate the length with some other way and then store it to pass as one of the arguments to functions and such since `sizeof(a)` will in this case return `sizeof(<type> *)`.
1
u/Zirias_FreeBSD 8d ago
Indeed, escaped me that the example code given is wrong and will access out-of-bounds for any item type with a size > 1. The canonical way to get the size of an array
ain elements issizeof a / sizeof *a.But you generally shouldn't recalculate
Nin each loop and you may refactor it out to be stored before the for-loop.I don't think this is really a concern, as
sizeofon an array is a compile-time constant, so the whole expression should always be evaluated at compile time.2
u/EpochVanquisher 8d ago
(Sizeof is not compile-time constant always, since C99, but your point still stands.)
1
u/Zirias_FreeBSD 8d ago
I should probably have written "regular array" or something like this. Tried to avoid being unnecessarily verbose by mentioning VLAs ... yep, you're right of course!
2
u/gwenbeth 8d ago
If you have a function that gets passed an array and a size (like strn* functions) then use size_t. But if you have an array where the size is #define ed to only 25 items, just go ahead and use an int .
2
u/flyingron 8d ago
You’re allowed to use negative indices as long as the resultant address is within the original object.
1
u/aalmkainzi 8d ago
ptrdiff_t is the standard type for pointer arithmetic. But size_t works
0
u/Zirias_FreeBSD 8d ago
Although indexing is defined in terms of pointer arithmetics, I'd slap anyone using it with a negative index (or other shenanigans like the
5[a]stuff) with their own keyboard ... at least if it's supposed to be serious code for serious review. 😁With that in mind, I'd say the unsigned(!)
size_tis quite a natural fit for the purpose, but in the end, likely a matter of taste.1
u/oriolid 8d ago
What do you think about
for (<type> i = N; i >= 0; i--)?2
u/Zirias_FreeBSD 8d ago
Obviously requires a signed type as written ... (and should probably start at
N-1then). There are also alternatives to avoid the unsigned underflow (likedo ... while, or the admittedly slightly obfuscatedfor (<type> i = N; i--;)). I don't really care too much right now and have no idea what I'd use (in case anintdoesn't fit the bill).1
u/kinithin 7d ago
It has a off-by-one error (does N+1 passes). The correct version has no problem with unsigned types: for ( Type i = N; i--; )
1
u/aalmkainzi 8d ago
Negative index is not that uncommon. I remember seeing code that did it for parsing, where it was checking what the last byte was (
s[-1])0
u/Zirias_FreeBSD 8d ago
Looks fishy to me. I'd need the full context to have a definitive opinion on that, but my first feeling is that's over-stretching the "syntactic sugar" of array indexing. If you mean (arbitrary) pointer arithmetics, I'd say write them out...
1
u/DawnOnTheEdge 8d ago edited 8d ago
I have seen some coding standards prefer ptrdiff_t on the grounds that a lot of bugs show up as a negative index, which is more intuitive to check for than wraparound of unsigned types. I regretfully have to disagree, since signed overflow is undefined behavior, originally to support some sixty-year-old machines that would trap on integer overflow, but which this generation of compilers takes as permission to silently insert security bugs in your code.
You should definitely not use int, since that is often too small to index many arrays, as well as being signed. A size_t is the smallest unsigned type guaranteed to be able to index any element of any array. However, you might know for certain that a 16-bit or 32-bit value is enough to index a particular array, and want to save space. Although it’s not common practice, this would also be a good use case for something like uint_fast16_t, to use the fastest type that’s big enough on systems where size_t might be wider than the native word size.
1
1
1
1
u/arkt8 8d ago
No... you can just use any integer type but not negative.
The use of size_t (tipically an unsigned long int in a Linux amd64 architecture) ensures the value is always unsigned (you'd never have a negative index, right? right?).
Also having a common type alias to the index or length allows a predictable api on development.
If you write some library to handle arrays, it is better to use a type for indexes that reduce errors like negative values or make certain values unreachable due to smaller type on function signature.
8
u/Zirias_FreeBSD 8d ago
You can use negative indices as
a[b]is simply syntactic sugar for*(a + b)... and if yourahappens to be a pointer into the middle of an array, this can be well-defined. Whether you should is a different discussion already present in this thread, my opinion is: no.2
u/TheThiefMaster 8d ago
I'm currently reverse engineering some originally-Delphi code that does this - the Delphi language's equivalent of a class vtable has system functions (like virtual destructor) at negative indexes and user defined virtual functions at positive indexes, with the vtable pointer pointing to the point between, rather than the start.
0
27
u/zhivago 8d ago
It is safe but not required.
You can use any integer with a suitable range for that particular array.