I’d love to know the rationale MS had for keeping the truncation behavior of GlobalMemoryStatus rather than adopting a more logically “safe” clamping behaviour. Software is terrible but I really can’t imagine software that would break with clamping that would not break with truncating.
It does/is clamping1. I expect the actual issue is the install script doesn’t use unsigned integers or the install tool doesn’t support them2. If you don’t compile with largeaddressaware it will clamp to 2GB-1 instead of 4 which probably made it look good at the time the snippet was written and/or they never tested on machines with 4GB.
In summary a 32bit chunk of code (the installer) was compiled as either 64bit or with largeaddressaware without the code actually being those things.
The documentation explicitly states it returns -1 to indicate overflow:
On computers with more than 4 GB of memory, the GlobalMemoryStatus function can return incorrect information, reporting a value of –1 to indicate an overflow.
I think you’re misunderstanding LARGEADDRESSAWARE, which IIRC is not about handling 64 bit addresses, but rather about having more than 2gig apparently (I recalled windows reserving the top gig of the address space, but this doc implies it was actually 2gb).
So here is what the docs say happen:
If you have <=2gb of memory it returns the correct amount
If you have >2gb and <=4gb you it will report 2gb, unless you compile with /LARGEADDRESSAWARE, in which case you get the correct amount reported
If you have more than 4gb it returns -1, but as u/david_chisnall says that should still be unsigned and so 4gb.
50/50 the devs just wrote
int memsize = status.dwAvailVirtual
or something.
It’s also worth noting GlobalMemoryStatusEx actually has a return value to indicate success. Which I’m guessing is never actually checked :D
If you have more than 4Gb, it treats this as (unsigned)-1, but if the program is 32 bit and not /LARGEADDRESSAWARE, that value is greater than 2Gb, so the value is then reduced to 2^31-1.
Btw, I’m the one who debugged the thing in the RaymondC article. Reading this one seemed strange due to this exact point - I know for sure this code limits normal 32 bit binaries to 2^31-1. Part of the reason is because of programs using signed types. So I think lcapaldo is right - this was either a 64 bit program, or was marked /LARGEADDRESSAWARE, but both of those seem very strange given its release date. Unfortunately I don’t have the binaries to check how it got here.
I’m a bit confused by the bug because the docs say that the relevant fields are all SIZE_T, which I’d assumed would be an unsigned value the size of an address space. Other docs support this. Suggesting that it’s a ULONG_PTR.
A value of -1 cast to such a type should give the largest positive value that can fit in the value, so the documented bug is that it can not represent more than 4 GiB - 1 B and clamps to that size for larger values. Comparing this to 64 MiB should be fine and it should also be fine to use this function if that’s all that you care about.
I’d love to know the rationale MS had for keeping the truncation behavior of
GlobalMemoryStatus
rather than adopting a more logically “safe” clamping behaviour. Software is terrible but I really can’t imagine software that would break with clamping that would not break with truncating.It does/is clamping1. I expect the actual issue is the install script doesn’t use unsigned integers or the install tool doesn’t support them2. If you don’t compile with largeaddressaware it will clamp to 2GB-1 instead of 4 which probably made it look good at the time the snippet was written and/or they never tested on machines with 4GB.
In summary a 32bit chunk of code (the installer) was compiled as either 64bit or with largeaddressaware without the code actually being those things.
The documentation explicitly states it returns -1 to indicate overflow:
I think you’re misunderstanding LARGEADDRESSAWARE, which IIRC is not about handling 64 bit addresses, but rather about having more than 2gig apparently (I recalled windows reserving the top gig of the address space, but this doc implies it was actually 2gb).
So here is what the docs say happen:
50/50 the devs just wrote
or something.
It’s also worth noting GlobalMemoryStatusEx actually has a return value to indicate success. Which I’m guessing is never actually checked :D
Correct. That is my entire point. The installer scripting language doesn’t have an unsigned type.
64bit just comes in in the sense that 64 bit implies the behavior of LARGEADDRESSAWARE.
If you have more than 4Gb, it treats this as (unsigned)-1, but if the program is 32 bit and not /LARGEADDRESSAWARE, that value is greater than 2Gb, so the value is then reduced to 2^31-1.
Btw, I’m the one who debugged the thing in the RaymondC article. Reading this one seemed strange due to this exact point - I know for sure this code limits normal 32 bit binaries to 2^31-1. Part of the reason is because of programs using signed types. So I think lcapaldo is right - this was either a 64 bit program, or was marked /LARGEADDRESSAWARE, but both of those seem very strange given its release date. Unfortunately I don’t have the binaries to check how it got here.
I’m a bit confused by the bug because the docs say that the relevant fields are all SIZE_T, which I’d assumed would be an unsigned value the size of an address space. Other docs support this. Suggesting that it’s a ULONG_PTR.
A value of -1 cast to such a type should give the largest positive value that can fit in the value, so the documented bug is that it can not represent more than 4 GiB - 1 B and clamps to that size for larger values. Comparing this to 64 MiB should be fine and it should also be fine to use this function if that’s all that you care about.