Можно ли отличать size_t от unsigned long int?

Мне нужен переносимый способ распечатать значение переменной n типа size_t. Поскольку я использую ANSI C89, я не могу использовать модификатор длины z. Мой текущий подход заключается в том, чтобы присвоить значение long unsigned int:

printf("%lu\n", (long unsigned int) n);

При условии, что size_t определяется как unsigned int или long unsigned int, я не вижу, как это произойдет. Безопасен ли литье?

Ответ 1

В C89, size_t определяется как целое число без знака. В отличие от будущих стандартов, C89 определяет, что представляет собой список беззнаковых целочисленных типов:

  • unsigned char
  • unsigned short
  • unsigned int
  • unsigned long

Таким образом, size_t в C89 никогда не будет больше, чем unsigned long, и поэтому литье всегда безопасно - и в том, что оно не вызовет никакого поведения undefined и в том, что оно всегда будет достаточно большим, чтобы удерживайте значение целиком.

Стоит отметить; стандарт C89 гласит: "Соответствующая реализация может иметь расширения (включая дополнительные библиотечные функции) при условии, что они не изменят поведение любой строго соответствующей программы" Это означает, что никакое расширение не может изменить это поведение - при этом все еще соответствует стандарту C89, поскольку неподписанные целые типы были специально перечислены и поэтому не могут быть изменены.

В будущих стандартах это не является гарантией, и пока вы не получите поведение undefined - вы можете потерять данные, где unsigned long меньше, чем size_t, что означает, что вы будете показывать неверные данные своему пользователю. В этой ситуации я бы не стал называть его "безопасным".


В качестве важной дополнительной заметки этот ответ относится к компиляторам, которые соответствуют стандарту C89. Возможно, ваш компилятор C89 будет "менее совместимым" в отношении вышеизложенного, и в этом случае - считайте, что поведение похоже на поведение на C99 или новее, где вы не увидите поведение undefined, но могут пострадать от потери данных если size_t больше, чем unsigned long. Чтобы быть ясным, это не соответствовало бы стандарту C89.

Помимо этого, хотя моя интерпретация стандарта (1.7 Compliance) заключается в том, что, хотя он утверждает, что расширения не должны изменять поведение "строго соответствующей программы" и, как таковое, не могут изменить тот факт, что size_t должен быть unsigned long по большей мере без соблюдения; это не меняет того факта, что такие расширения существуют. Например, GNU GCC предоставляет расширение, которое добавляет unsigned long long. На мой взгляд, это несоответствие, но на самом деле вы должны быть готовы справиться с такими вещами и как таковыми - , в то время как стандарт говорит, что вы делаете, полностью безопасен, вы должны быть готовы к потенциальной потере данных, несовместимые компиляторы или расширения используются.


Пожалуйста, смотрите здесь, чтобы обсудить эту тему: fooobar.com/questions/848639/...

Ответ 2

size_t n = foo();
printf("%lu\n", (long unsigned int) n);

При условии, что size_t определяется как unsigned int или long unsigned int... Является ли приведение безопасным?

Да, приведение является безопасным без неопределенного поведения и потери информации на C89, C99, C11.


Но что происходит , если это условие не соответствует действительности?

Предполагая, что диапазон size_t будет в пределах диапазона unsigned long, это очень разумно. Добавить тест времени компиляции: ref

#include <limits.h>
#if defined(__STDC__)
#if defined(__STDC_VERSION__)
#if (__STDC_VERSION__ >= 199901L)
#include <stdint.h>
#if SIZE_MAX > ULONG_MAX
#error Re-work printf size code
#endif
#endif
#endif
#endif

Дело в том, что когда у кода была зависимость - добавьте тест. Даже если это приемлемо на всех известных машинах сегодня и исторически, будущее имеет неизвестные.

C сегодня, с его огромной гибкостью, позволяет SIZE_MAX > ULONG_MAX, но это, безусловно, редко. ИМО, SIZE_MAX > ULONG_MAX находится за гранью.


Такие тесты распространены, так как время от времени, хотя это возможно, просто нереально или не запланировано написать супер переносимый код.

#include <limits.h>
#if CHAR_BIT != 8 && CHAR_BIT != 16 && CHAR_BIT != 32 && CHAR_BIT != 64
  #error Code depends on char size as a common power of 2.
#endif

Мне нужен портативный способ для печати значения переменной n типа size_t.

Тем не менее, для достижения цели верхнего уровня OP, можно написать простую портативную вспомогательную функцию.

// This approach works with any unsigned type
void print_size_t(size_t n) {
  if (n >= 10) print_size_t(n/10);
  putchar((int) (n%10) + '0');
}

Чтобы избежать рекурсии, немного более длинная функция:

#include <limits.h>
void print_size_t(size_t n) {
  char buf[sizeof n * CHAR_BIT / 3 + 2];  // 1/3 is more than log2(10)
  char *p = &buf[sizeof buf - 1];          // Start at end of buf[]
  *p = '\0';
  do {
    p--;
    *p = (char) (n%10 + '0');
    n /= 10;
  } while (n);    // Use a do {} while so print_size_t(0) prints something
  fputs(p, stdout);
}