c: unsigned long long присваивается неправильное значение только для HP-UX

Были небольшие проблемы с придумыванием названия для этого вопроса.

Недавно я погрузился в мир C.

У меня есть небольшой код, который в основном показывает емкость и свободное место на диске. Он отлично работает на нескольких разных дистрибутивах Linux, которые я пробовал, а также на Solaris и AIX. Недавно я скомпилировал на коробке HP-UX PA-RISC и получил (на мой взгляд) действительно странную ошибку.

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

В GDB, когда я делаю:

p (fsStat.f_bsize * fsStat.f_blocks)

Результат 1335205888 Но после запуска вычисления, когда я делаю

p totalByes

Результат 18446744071562067968

Любая информация, которая могла бы даже дать мне представление о том, что здесь можно попробовать, была бы действительно здоровой. Раньше я думал, что знаю, как программировать, пока не начал делать мультиплатформенный C :(


person Ruairi O'Brien    schedule 08.08.2013    source источник
comment
В большинстве случаев тип выражения C (который определяет условия, при которых оно будет переполнено) определяется самим выражением, а не контекстом, в котором оно появляется. fsStat.f_bsize * fsStat.f_blocks оценивается с типом, определяемым типами его операндов, а не типом, в который преобразуется результат. Приведение операндов к unsigned long long должно решить проблему. (И окончательное приведение не требуется; присваивание или инициализатор неявно преобразует любое числовое выражение в целевой тип.)   -  person Keith Thompson    schedule 08.08.2013
comment
Спасибо за ваш комментарий. Вы помогли мне лучше понять, как обрабатывается заявление. Теперь я понимаю ошибку, связанную с заключением умножения в круглые скобки, что приводит к значению этого типа (которое в данном случае переполняется). Изначально у меня не было гипса или круглых скобок, и я просто пробовал. Ваше решение было правильным и теперь имеет для меня гораздо больше смысла. Спасибо.   -  person Ruairi O'Brien    schedule 09.08.2013


Ответы (2)


Гипотеза:

Умножение переполнилось, поэтому fsStat.f_bsize * fsStat.f_blocks дал результат переполнения -2 147 483 648. Когда это было преобразовано в unsigned long long, было получено 18446744071562067968, что равно 0xffffffff80000000, результат переноса -2147483648 в 64-битный формат без знака. GDB использует другую арифметику, чем C, поэтому он отображает математически правильный результат.

Чтобы исправить это, измените (unsigned long long) (fsStat.f_bsize * fsStat.f_blocks) на (unsigned long long) fsStat.f_bsize * fsStat.f_blocks, чтобы преобразовать в более широкий целочисленный формат перед умножением.

Лучше, чем unsigned long long, было бы использовать либо uint64_t (из <stdint.h>), либо тип, предоставляемый платформой (какой-то заголовок Linux) для работы с размерами дисков.

person Eric Postpischil    schedule 08.08.2013
comment
Какой удивительный ответ. Вы были абсолютно правы, конечно. Печать реального значения умножения действительно дала -2147483648. Решение, как вы сказали, заключалось в том, чтобы приводить вот так (unsigned long long) fsStat.f_bsize * (unsigned long long) fsStat.f_blocks. Я попытался использовать ‹stdint.h›, чтобы получить uint64_t, но он недоступен на машине HP-UX, которую я использую. Я подозревал некоторую форму проблемы с памятью (переполнение), но очень не знал, как с этим справиться. Не могу отблагодарить вас достаточно. - person Ruairi O'Brien; 09.08.2013

Я предполагаю, что f_bsize и f_blocks относятся к типу int. Значение, вероятно, переполняется отрицательным значением.

Попробуйте привести каждое из этих значений к unsigned long long перед их умножением.

person Gort the Robot    schedule 08.08.2013
comment
Лучший способ, чем приведение, - это использовать правильный тип в первую очередь, который в этом случае должен быть size_t - person Jocke; 08.08.2013
comment
@Jocke: size_t гарантированно подходит для размеров объектов в памяти. Размеры дискового пространства, возможно, должны быть больше. - person Eric Postpischil; 08.08.2013
comment
В 64-битных системах size_t должен иметь ширину 64 бита. что может представлять около 18 446 744 ТБ. У вас столько места на диске? Но да, я полагаю, что было бы безопасно следовать стандарту и не использовать size_t для дискового пространства. - person Jocke; 08.08.2013
comment
Спасибо за ответ и комментарии. Когда я просматривал документы, я думал, что они определены как длинные: linux.about.com/ библиотека/cmd/blcmdl2_statfs.htm. Когда я сделал 'p sizeof(fsStat.f_bsize)' в GDB, результатом было 4, что объясняет переполнение. На этой 64-битной машине HP-UX p sizeof(long) на самом деле равен 4, тогда как в других 64-битных системах, как я заметил, это 8. Урок усвоен. - person Ruairi O'Brien; 09.08.2013
comment
Да, иногда расстраивает то, что размеры определены более строго. - person Gort the Robot; 09.08.2013