Какой тип используется в C ++ для определения размера массива? - PullRequest
0 голосов
/ 15 января 2019

Компиляция некоторого тестового кода в avr-gcc для 8-битного микроконтроллера, строка

const uint32_t N = 65537;
uint8_t values[N]; 

Я получил следующее предупреждение компиляции (по умолчанию должно быть ошибка, действительно)

warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '65537' to '1' [-Woverflow]
uint8_t values[N];

Обратите внимание, что при компиляции для этой цели sizeof(int) равно 2.

Таким образом, кажется, что размер массива не может превышать размер unsigned int.

Я прав? Это специфично для GCC или является частью какого-то стандарта C или C ++?

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

Ответы [ 6 ]

0 голосов
/ 16 января 2019

[Этот ответ был написан, когда вопрос был помечен C и C ++. Я еще не пересмотрел его в свете откровения OP, что они используют C ++, а не C.]

size_t - это тип, который стандарт C обозначает для работы с объектами разных размеров. Однако это не панацея для получения правильных размеров.

size_t должен быть определен в заголовке <stddef.h> (а также в других заголовках).

Стандарт C не требует, чтобы выражения для размеров массива, если они указаны в объявлениях, имели тип size_t, и при этом он не требует, чтобы они помещались в size_t. Не указано, что должна делать реализация C, когда она не может удовлетворить запрос на размер массива, особенно для массивов переменной длины.

В вашем коде:

const uint32_t N = 65537;
uint8_t values[N];

values объявлен как массив переменной длины. (Хотя мы видим, что значение N может быть легко известно во время компиляции, оно не соответствует определению константного выражения в C, поэтому uint8_t values[N]; квалифицируется как объявление массива переменной длины.) Как вы заметили, GCC предупреждает, что 32-разрядное целое число без знака N сужается до 16-разрядного целого числа без знака. Это предупреждение не требуется стандартом C; это любезно предоставлено компилятором. Более того, преобразование вообще не требуется - поскольку стандарт C не определяет тип для измерения массива, компилятор может принять любое целочисленное выражение здесь. Поэтому тот факт, что он вставил неявное преобразование в тип, который ему необходим для измерений массива, и предупредил вас об этом, является особенностью компилятора, а не стандарта C.

Подумайте, что бы произошло, если бы вы написали:

size_t N = 65537;
uint8_t values[N];

Теперь в uint8_t values[N]; не будет предупреждений, поскольку 16-битное целое число (ширина size_t в вашей реализации C) используется там, где требуется 16-битное целое число. Однако в этом случае ваш компилятор, скорее всего, выдаст предупреждение в size_t N = 65537;, так как 65537 будет иметь 32-битный целочисленный тип, и сужающее преобразование выполняется во время инициализации N.

Однако тот факт, что вы используете массив переменной длины, предполагает, что вы можете вычислять размеры массива во время выполнения, и это только упрощенный пример. Возможно, ваш реальный код не использует постоянные размеры, как это; это может вычислить размеры во время выполнения. Например, вы можете использовать:

size_t N = NumberOfGroups * ElementsPerGroup + Header;

В этом случае существует вероятность того, что неверный результат будет рассчитан. Если все переменные имеют тип size_t, результат может легко переноситься (эффективно выходя за пределы типа size_t). В этом случае компилятор не будет предупреждать вас, потому что значения имеют одинаковую ширину; нет сужающего преобразования, только переполнение.

Поэтому использование size_t недостаточно для защиты от ошибок в измерениях массива.

Альтернативой является использование типа, который вы ожидаете, будет достаточно широким для ваших вычислений, возможно, uint32_t Учитывая NumberOfGroups и такие как uint32_t типы, тогда:

const uint32_t N = NumberOfGroups * ElementsPerGroup + Header;

даст правильное значение для N. Затем вы можете проверить его во время выполнения, чтобы защититься от ошибок:

if ((size_t) N != N)
    Report error…

uint8_t values[(size_t) N];
0 голосов
/ 15 января 2019

Я расскажу о проблеме с правилами в черновом варианте стандарта ISO CPP "Неправильный и неполный" n4659 . Акцент добавлен мной.

11.3.4 определяет объявления массива. Первый абзац содержит

Если присутствует константное выражение [в квадратных скобках] (8.20), оно должно быть преобразованным константным выражением типа std :: size_t [...].

std::size_t от <cstddef> и определяется как

[...] определяемый реализацией целочисленный тип без знака, достаточно большой, чтобы содержать размер в байтах любого объекта.

Поскольку он импортируется через заголовки стандартной библиотеки C, стандарт C имеет отношение к свойствам size_t. Проект ISO C N2176 предписывает в 7.20.3 "минимальные максимумы", если хотите, целочисленных типов. Для size_t этот максимум составляет 65535. Другими словами, 16-битный size_t полностью соответствует.

«Преобразованное константное выражение» определено в 8.20 / 4:

A преобразованное константное выражение типа T является выражением, неявно преобразованным в тип T, где преобразованное выражение является константным выражением, а последовательность неявного преобразования содержит только [любое из 10 различных преобразований, одно из что касается целых чисел (пар. 4.7):]

- интегральные преобразования (7,8) , кроме сужающих преобразований (11.6.4)

Интегральное преобразование (в отличие от повышения , которое изменяет тип на эквивалентные или более крупные типы) определяется следующим образом (7.8 / 3):

Значение типа целого можно преобразовать в значение другого целого типа.

7,8 / 5 затем исключает интегральные повышения из интегральных преобразований. Это означает, что преобразования обычно сужают изменения типа .

Сужающие преобразования (которые, как вы помните, исключены из списка разрешенных преобразований в преобразованных константных выражениях , используемых для размеров массивов), определяются в контексте списка -инициализация, 11.6.4, пар. 7

Сужающее преобразование - это неявное преобразование
[...]
7.3 1 - от целочисленного типа [...] до целочисленного типа, который не может представлять все значения исходного типа, за исключением случаев, когда источником является константное выражение, значение которого после целочисленных повышений будет вписывается в целевой тип.

Это фактически говорит о том, что эффективный размер массива должен быть постоянным значением при отображении, что является вполне разумным требованием для избежания неожиданностей.


Теперь давайте все вместе поковыряем. Рабочая гипотеза состоит в том, что std::size_t является 16-разрядным целочисленным типом без знака со значением в диапазоне 0,65535. Целочисленный литерал 65537 не представлен в 16-битном системном unsigned int и, следовательно, имеет тип long. Поэтому он будет подвергаться целочисленному преобразованию. Это будет сужающее преобразование , поскольку значение не может быть представлено в 16-битном size_t 2 , так что условие исключения в 11.6.4 / 7.3, «значение подходит в любом случае», не применяется.

Так что это значит?

11.6.4 / 3.11 - это правило всеохвата для сбоя при создании значения инициализатора из элемента в списке инициализатора. Поскольку правила списков инициализаторов используются для размеров массивов, мы можем предположить, что универсальное условие сбоя преобразования применяется к константе размера массива:

(3.11) - в противном случае программа будет некорректной.

Для выполнения диагностики требуется соответствующий компилятор. Дело закрыто.


1 Да, они подразделяют пункты.

2 Преобразование целочисленного значения 65537 (в любом типе, который может содержать число & mdash; здесь, вероятно, `long) в 16-битовое целое число без знака является определенной операцией. 7,8 / 2 детали:

Если тип назначения является беззнаковым, полученное значение является наименее целым числом без знака, соответствующим исходному целое число (по модулю 2 n , где n - количество битов, используемых для представления типа без знака). [Примечание: в двух дополняющее представление, это преобразование является концептуальным, и в битовой комбинации нет изменений (если есть без усечения). —Конечная записка]

Двоичное представление 65537 равно 1_0000_0000_0000_0001, т. Е. Устанавливается только младший значащий бит из младших 16 бит. Преобразование в 16-битное значение без знака (которое указывает косвенное свидетельство size_t is) вычисляет [значение выражения] по модулю 2 ^ 16, то есть просто принимает младшие 16 бит. Это приводит к значению 1, указанному в диагностике компилятора.

0 голосов
/ 15 января 2019

Так что кажется, что при размере массива не может превышать размер unsigned int.

Это, похоже, имеет место в вашей конкретной реализации C [++] .

Я прав? Это специфично для gcc или является частью какого-то C или C ++? стандарт

Это не является характеристикой GCC в целом и не определяется ни стандартом C, ни C ++. Это характеристика вашей конкретной реализации: версия GCC для вашей конкретной вычислительной платформы.

Стандарт C требует, чтобы выражение, обозначающее количество элементов массива, имело целочисленный тип, но не указывает конкретный тип. Мне кажется странным, что ваш GCC, кажется, утверждает, что он дает вам массив с другим количеством элементов, чем вы указали. Я не думаю, что это соответствует стандарту, и я не думаю, что это имеет смысл как расширение. Я бы предпочел, чтобы вместо этого он отклонял код.

0 голосов
/ 15 января 2019

В вашей реализации size_t определяется как unsigned int, а uint32_t определяется как long unsigned int. При создании массива C аргумент для размера массива неявно преобразуется компилятором в size_t.

Вот почему вы получаете предупреждение. Вы задаете аргумент размера массива с uint32_t, который преобразуется в size_t, и эти типы не совпадают.

Это, вероятно, не то, что вы хотите. Вместо этого используйте size_t.

0 голосов
/ 15 января 2019

Значение, возвращаемое sizeof, будет иметь тип size_t.

Обычно используется как количество элементов в массиве, потому что оно будет иметь достаточный размер. size_t всегда без знака, но это определяется реализацией, какой это тип. Наконец, определяется реализацией, может ли реализация поддерживать объекты размером SIZE_MAX байт ... или даже близко к нему.

0 голосов
/ 15 января 2019

size_t считается используемым типом, несмотря на то, что он не был официально ратифицирован ни стандартами C, ни C ++.

Основанием для этого является то, что sizeof(values) будет таким типом (что равно в соответствии со стандартами C и C ++), и число элементов обязательно будет не больше этого, поскольку sizeof для объекта не менее 1.

...