Присвойте результат sizeof () ssize_t - PullRequest
3 голосов
/ 22 марта 2019

Мне пришло в голову, что мне нужно сравнить результат sizeof(x) с ssize_t.

Конечно, GCC выдал ошибку (повезло мне (я использовал -Wall -Wextra -Werror)), и я решил сделать макрос, чтобы иметь подписанную версию sizeof().

#define ssizeof (ssize_t)sizeof

И тогда я могу использовать это так:

for (ssize_t i = 0; i < ssizeof(x); i++)

Проблема в том, есть ли у меня гарантии, что SSIZE_MAX >= SIZE_MAX? Я думаю, что, к сожалению, это никогда не будет правдой.

Или, по крайней мере, это sizeof(ssize_t) == sizeof(size_t), которое сократило бы половину значений, но все равно было бы достаточно близко.

Я не нашел никакой связи между ssize_t и size_t в документации POSIX.

Похожие вопросы:

Какой тип следует использовать для цикла по массиву?

Ответы [ 4 ]

5 голосов
/ 22 марта 2019

Нет гарантии, что SSIZE_MAX >= SIZE_MAX. На самом деле, это очень маловероятно, поскольку size_t и ssize_t, вероятно, соответствуют соответствующим типам без знака и со знаком, поэтому (на всех реальных архитектурах) SIZE_MAX > SSIZE_MAX. Преобразование неподписанного значения в подписанный тип, который не может содержать это значение, является неопределенным поведением. Технически, ваш макрос проблематичен.

На практике, по крайней мере, на 64-битных платформах вы вряд ли столкнетесь с проблемами, если значение, которое вы конвертируете в ssize_t, является размером реально существующего объекта. Но если объект теоретический (например, sizeof(char[3][1ULL<<62])), вы можете получить неприятный сюрприз.

Обратите внимание, что единственным допустимым отрицательным значением типа ssize_t является -1, что является признаком ошибки. Вы можете путать ssize_t, который определен Posix, с ptrdiff_t, который определен в стандарте C начиная с C99. Эти два типа одинаковы на большинстве платформ и обычно представляют собой целочисленный тип со знаком, соответствующий size_t, но ни один из этих режимов не гарантируется ни одним из стандартов. Однако семантика двух типов различна, и вы должны знать об этом, когда используете их:

  • ssize_t возвращается несколькими интерфейсами Posix, чтобы позволить функции сигнализировать либо количество обработанных байтов, либо указание ошибки; индикация ошибки должна быть -1. Не ожидается, что любой возможный размер поместится в ssize_t; обоснование Posix гласит:

    Соответствующее приложение будет ограничено, чтобы не выполнять ввод / вывод кусками, большими, чем {SSIZE_MAX}.

    Это не проблема для большинства интерфейсов, которые возвращают ssize_t, потому что Posix обычно не требует интерфейсов для гарантии обработки всех данных. Например, read и write принимают size_t, который описывает длину буфера для чтения / записи, и возвращают ssize_t, который описывает количество байтов, фактически прочитанных / записанных; это означает, что не более SSIZE_MAX байтов будут считываться / записываться, даже если доступно больше данных. Однако в обосновании Posix также отмечается, что конкретная реализация может обеспечить расширение, которое позволяет обрабатывать большие блоки («соответствующее приложение, использующее расширения, сможет использовать полный диапазон, если реализация предоставит расширенный диапазон»), идея заключается в том, чтобы что реализация может, например, указать, что возвращаемые значения, отличные от -1, должны интерпретироваться путем приведения их к size_t. Такое расширение не будет переносимым; на практике большинство реализаций ограничивают количество байтов, которые могут быть обработаны за один вызов, числом, которое может быть указано в ssize_t.

  • ptrdiff_t - это (в стандарте C) тип результата разницы между двумя указателями. Чтобы вычитание указателей было правильно определено, два указателя должны ссылаться на один и тот же объект, либо указывая на объект, либо указывая на байт, следующий сразу за объектом. Комитет C признал, что если ptrdiff_t является подписанным эквивалентом size_t, то, возможно, разница между двумя указателями может быть не представимой, что приводит к неопределенному поведению, но они предпочли, чтобы требование ptrdiff_t было больший тип, чем size_t. Вы можете поспорить с этим решением - многие люди приняли - но оно действует со времен C90, и вряд ли оно изменится сейчас. (Нынешняя стандартная формулировка, & sect; 6.5.6 / 9: «Если результат не может быть представлен в объекте этого типа [ptrdiff_t], поведение не определено.»)

    Как и в Posix, стандарт C не определяет неопределенное поведение, поэтому было бы ошибкой интерпретировать это как , запрещающее вычитание двух указателей в очень больших объектах. Реализации всегда разрешено определять результат поведения, оставленного неопределенным стандартом, так что для реализации вполне допустимо указывать, что если P и Q являются двумя указателями на один и тот же объект, где P >= Q, то (size_t)(P - Q) - математически правильная разница между указателями, даже если вычитание переполняется. Конечно, код, который зависит от такого расширения, не будет полностью переносимым, но если расширение является достаточно распространенным, это не может быть проблемой.

В качестве конечной точки, неоднозначность использования -1 как в качестве индикации ошибки (в ssize_t), так и в качестве, возможно, преобразуемого результата вычитания указателя (в ptrdiff_t) вряд ли будет присутствовать на практике при условии что size_t равно указателю. Если size_t равен указателю, математически правильное значение P-Q может быть (size_t)(-1) (иначе SIZE_MAX), если объект, на который ссылаются P и Q, имеет size SIZE_MAX, который, исходя из предположения, что size_t равна ширине указателя, подразумевает, что объект плюс следующий байт занимают все возможные значения указателя. Это противоречит требованию, чтобы какое-то значение указателя (NULL) отличалось от любого допустимого адреса, поэтому мы можем заключить, что истинный максимальный размер объекта должен быть меньше SIZE_MAX.

3 голосов
/ 22 марта 2019

Обратите внимание, что вы на самом деле не можете этого сделать.

Самый большой из возможных объектов в Linux x86 имеет размер чуть меньше 0xB0000000, а SSIZE_T_MAX равен 0x7FFFFFFF.

Я не проверял, может ли read и прочее действительно обрабатывать максимально большие объекты, но если они могут, это работает так:

ssize_t result = read(fd, buf, count);
if (result != -1) {
    size_t offset = (size_t) result;
    /* handle success */
} else {
    /* handle failure */
}

Вы можете обнаружить, что libc обанкротился. Если это так, это будет работать, если ядро ​​хорошо:

ssize_t result = sys_read(fd, buf, count);
if (result >= 0 || result < -256) {
    size_t offset = (size_t) result;
    /* handle success */
} else {
    errno = (int)-result;
    /* handle failure */
}
2 голосов
/ 22 марта 2019

Я собираюсь принять это как проблему X-Y. Проблема в том, что вы хотите сравнить число без знака с номером без знака. Вместо того, чтобы приводить результат от sizeof к ssize_t, вы должны проверить, является ли ваше значение ssize_t меньше нуля. Если это так, то вы знаете, что оно меньше, чем ваше size_t значение. Если нет, то вы можете привести его к size_t и затем сделать сравнение.

Например, вот функция сравнения, которая возвращает -1, если номер со знаком меньше номера без знака, 0, если он равен, или 1, если номер со знаком больше номера без знака:

int compare(ssize_t signed_number, size_t unsigned_number) {
    int ret;
    if (signed_number < 0 || (size_t) signed_number < unsigned_number) {
        ret = -1;
    }
    else {
        ret = (size_t) signed_number > unsigned_number;
    }
    return ret;
}

Если все, что вам нужно, это эквивалент операции <, вы можете пойти немного проще с чем-то вроде этого:

(signed_number < 0 || (size_t) signed_number < unsigned_number))

Эта строка даст вам 1, если signed_number меньше unsigned_number, и у нее нет накладных расходов ветвления. Просто требуется дополнительная < операция и logical-OR.

1 голос
/ 22 марта 2019

ssize_t - это тип POSIX, он не определен как часть стандарта C. POSIX определяет, что ssize_t должен иметь возможность обрабатывать числа в интервале [-1, SSIZE_MAX], поэтому в принципе он даже не должен быть обычным типом со знаком. Причина этого немного странного определения в том, что единственное место, где используется ssize_t, это возвращаемое значение для чтения / записи / и т. Д. функции.

На практике это всегда обычный тип со знаком того же размера, что и size_t. Но если вы хотите быть по-настоящему педантичным к своим типам, вы не должны использовать его для других целей, кроме обработки возвращаемых значений для системных вызовов IO. Для общего целочисленного типа со знаком указателя размера C89 определяет ptrdiff_t. Который на практике будет таким же, как ssize_t.

Кроме того, если вы посмотрите на официальную спецификацию read () , вы увидите, что для аргумента 'nbyte' он говорит, что 'если значение nbyte больше, чем {SSIZE_MAX}, результат определяется реализацией. '. Таким образом, даже если size_t способен представлять большие значения, чем SSIZE_MAX, это поведение, определяемое реализацией, для использования больших значений, чем для системных вызовов IO (как упоминалось, единственные места, где используется ssize_t). И аналогичные для записи () и т. Д.

...