4

Had a bit of trouble coming up with a title for this question.

I recently fumbled in to the world of C.

I have a little bit of code that basically shows the capacity and free space of a drive. It works fine on a few different Linux distros I've tried as well as Solaris and AIX. I recently compiled on a HP-UX PA-RISC box and got (in my opinion) a really strange error.

struct statfs   fsStat;
err = statfs(rootPath,&fsStat);
unsigned long long totalBytes = (unsigned long long)(fsStat.f_bsize * fsStat.f_blocks);

In GDB when I do:

p (fsStat.f_bsize * fsStat.f_blocks)

The result is 1335205888 But after the calculation is run, when I do

p totalByes

The result is 18446744071562067968

Any information that might even give me an idea of what to try here would be really great. Used to think I knew how to program until I started doing multi platform C :(

Ruairi O'Brien
  • 1,209
  • 5
  • 18
  • 33
  • 2
    In most cases, the type of a C expression (which determines the conditions in which it will overflow) are determined by the expression itself, not by the context in which it appears. `fsStat.f_bsize * fsStat.f_blocks` is evaluated with a type determined by the types of its operands, not by the type the result is converted to. Casting the operands to `unsigned long long` should correct the problem. (And the final cast is unnecessary; an assignment or initializer implicitly converts any numeric expression to the target type.) – Keith Thompson Aug 08 '13 at 18:22
  • Thank you for your comment. You've helped me understand better how a statement is processed. I realise now the mistake in wrapping the multiplication in parenthisis, resulting in a value of that type (which overflowed in this case). Initially I had no cast or parenthises and was just trying it out. Your solution was the correct one and makes a lot more sense to me now. Thank you. – Ruairi O'Brien Aug 09 '13 at 08:27

2 Answers2

7

Hypothesis:

The multiplication overflowed, so fsStat.f_bsize * fsStat.f_blocks produced an overflow result of -2147483648. When this was converted to unsigned long long, it produced 18446744071562067968, which is 0xffffffff80000000, the result of wrapping -2147483648 in a 64-bit unsigned format. GDB uses different arithmetic than C, so it displayed the mathematically correct result.

To fix this, change (unsigned long long) (fsStat.f_bsize * fsStat.f_blocks) to (unsigned long long) fsStat.f_bsize * fsStat.f_blocks, to convert to the wider integer format before multiplication.

Better than unsigned long long would be to use either uint64_t (from <stdint.h>) or a type supplied by the platform (some Linux header) for working with disk sizes.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • What an amazing answer. You were absolutely correct of course. Printing the real value of the multiplication did produce -2147483648. The solution as you said was to cast like so (unsigned long long) fsStat.f_bsize * (unsigned long long) fsStat.f_blocks. I tried using to get uint64_t but not available on the HP-UX machine I'm using. I suspected some form of memory issue (overflow) but was very unsure of how to handle it. Can't thank you enough. – Ruairi O'Brien Aug 09 '13 at 08:06
4

My guess is that f_bsize and f_blocks are of type int. The value is probably overflowing into a negative value.

Try casting each of these values to unsigned long long before multiplying them.

Gort the Robot
  • 2,329
  • 16
  • 21
  • A better way than casting is to use the proper type in the first place, which in this case should be `size_t` – Jocke Aug 08 '13 at 18:18
  • 4
    @Jocke: `size_t` is guaranteed to be suitable for sizes of objects in memory. Sizes of disk spaces may need to be larger. – Eric Postpischil Aug 08 '13 at 18:19
  • On 64bit systems `size_t` should be 64 bits wide. which can represent about 18 446 744 TB. Do you have that much disk space? But yes, I suppose it would be safe to follow the standard and not use size_t for disk space. – Jocke Aug 08 '13 at 18:25
  • Thanks for the answer and comments. When I looked up the docs I thought they were defined as long: http://linux.about.com/library/cmd/blcmdl2_statfs.htm. When I did 'p sizeof(fsStat.f_bsize)' in GDB the result was 4 which would explain the overflow. On that 64 bit HP-UX box, p sizeof(long) is actually 4 whereas on the other 64 bit systems I notice it is 8. Lesson learned. – Ruairi O'Brien Aug 09 '13 at 08:22
  • Yeah, it's frustrating sometimes that sizes are defined more strictly. – Gort the Robot Aug 09 '13 at 16:23