Комментарии 35
Отлично! Полезное предупреждение. Спасибо за статью. Хоть и пользуюсь gcc-5.1 несколько недель, о таком предупреждении не знал. Нужно читать ChangeLog.
+3
а не первые 4/8 байт по размеру указателя?void something( char encryptionKey[9000] ) { // WHATEVER, PROFIT // this call is broken SecureZeroMemory( encryptionKey, sizeof(encryptionKey)); // erase they key }
здесь разработчики хотели перезаписать нулями какие-то данные, но из-за ошибки будет перезаписан только первый байт
+3
Да, вы правы. Спасибо.
+2
По размеру слова на таргете. А ошибка с передачей размера не отдельным аргументом — действительно детская ошибка, не знал что в С++ она распространена, думал, что они со своим STL уже и забыли про массивы.
0
Очень короткая память нужна, чтобы за какие-то три-четыре года со своим STL успеть забыть классику, однако.
+1
почему 3-4 года? это array, а вектору уже лет 20 =) а массив char — это std::string в большинстве случаев.
0
Вектору действительно много-много лет, но это ни разу не замена старому доброму массиву. Написать что-нибудь типа
В общем — да, потихоньку «сырые» массивы из C++ изживаются, но до полной замены ещё ой как далеко.
std::vector<int> a = { 1, 2 };
вы в С++98 не сможете. Это если даже забыть об эффективности. В C++11 зато можно написать foo(std::array<int, 3>({1, 2, 3}));
не создавая отдельной константы, что иногда удобнее, чем вариант с обычными массивами. Строки тоже дёргают конструкторы и размещают данные «в куче», что далеко не всегда хорошо. Посмотрите в код какого-нибудь Chrome и вы обнаружите, что там все строковые константы — это по прежнему const char *
и никак иначе! operator""s
— это даже не C++11, а С++14!В общем — да, потихоньку «сырые» массивы из C++ изживаются, но до полной замены ещё ой как далеко.
+2
Вроде как с 1993 года в STL есть vector, map и прочее. Я вектор уже в BCB 6 и MSVC 6 активно юзал, работало =)
+1
Ну как вам сказать, чтобы не обидеть… люди, которых не волнует тот факт, что массив из пары целых чисел занимает байт этак 40 и размещает данные «в куче» (то есть, к примеру, не может быть размещён как статический глобальный объект) склонны использовать python, и ruby, а не C++.
+2
Мне кажется, вы перегибаете палку, делая поспешные выводы о квалификации и/или личных предпочтениях оппонента. Ваш гипотетический массив из пары чисел (даже пары сотен) можно передать и вектором (ссылкой) с данными в куче, если функция вызывается редко, и у вас не жёсткий real-time. Профессионал должен быть в состоянии отличить микроскопический overhead от подлинного транжирства (вызова той же самой функции в цикле с N>1000 итераций, например).
Общее правило: когда пишешь код, используй мозг, а когда код уже написан — подключай профайлер.
Общее правило: когда пишешь код, используй мозг, а когда код уже написан — подключай профайлер.
+2
Мне кажется, вы перегибаете палку, делая поспешные выводы о квалификации и/или личных предпочтениях оппонента.Нет — я делаю выводы всего лишь о его неумении использовать адекватные инструменты.
Ваш гипотетический массив из пары чисел (даже пары сотен) можно передать и вектором (ссылкой) с данными в куче, если функция вызывается редко, и у вас не жёсткий real-time.Вы издеваетесь или зачем? Вот это вот: "пары чисел (даже пары сотен)" — это как? Это где? Это почему? Конечно массив в пару сотен элементов можно передать с данными в куче, на таким масштабах бедствие уже не смертельно. А вот как раз массив в два-три элемента так передавать не стоит, так как тогда у вас вспомогательные действия будут занимать раз в 10 больше времени, чем основная операция.
Профессионал должен быть в состоянии отличить микроскопический overhead от подлинного транжирства (вызова той же самой функции в цикле с N>1000 итераций, например).Давайте отделим мухи от котлет, а? Вызов функции, в которую передаётся массив в цикле с N>1000 итераций — это нормальный, вполне себе грамотный подход. Если функция объявлена как
inline
или испольщуется LTO — то нормальный компилятор (на сегодня — фактически любой современный компилятор кроме MSVC) все ваши «лишние телодвижения» схлопнет и время работы будет таким же, как если бы функции там и не было. А вот если у вас в этом месте будет создаваться вектор — тоды ой, избавляться от лишних аллокаций ни один из известных мне оптимизаторов не умеет.Общее правило: когда пишешь код, используй мозг, а когда код уже написан — подключай профайлер.Ну если ваша цель — программа, которая жрёт гигабайты и требует восьмиядерного процессора для того, чтобы справиться с задачей, над которой до того работал какой-нибудь PentiumMMX с 64MiB памяти — то да, этот подход имеет смысл. Но… Тут C#/Java точно сгодится, часто Python и Ruby тоже подойдут. Зачем вам в этом случае C++? C++ — это очень тяжёлый, сложный язык, работать с ним сложно. И единственное оправдание многим решениям, которые усложняют работу с ним — это его эффективность. Если же от неё отказываетесь, то зачем вообще весь огород городить?
+2
Да, древние хвосты вылезают и будут вылезать вечно… очевидная ошибка при проектировании языка — эквивалентность имени объекта массива и указателя на первый элемент массива (имя объекта структуры ведь не эквивалентно указателю на первый элемент… да это и невозможно).
+1
что в коде на C++ рекомендуется использовать следующий фокус (отсюда) с шаблонной функцией:Что только не придумают, лишь бы не использовать boost (boost::size).
0
Я занимаюсь проектированием собственного языка программирования, и могу сказать, что в языке кроме sizeof() должен быть оператор cardof() — для определения количества элементов любой агрегатной сущности (не только массива, но и структуры и т.п.). В С++ тоже не мешало бы такое ввести, только увы — не введут… так и будут использовать всякие хаки на шаблонах, которые в одном месте компилируются а в другом — нет.
0
<да начнется холивар> А я вот с некоторых пор стараюсь избегать буст, потому я хочу, чтоб код был самодостаточным (компилировался сразу после загрузки из системы контроля версий)—то есть буст надо класть в код—но даже минимальные плюшки из него, типа boost::array (хотя он уже не актуален), обычно тащат за собой мегабайты кода. Впрочем, я не уверен, сколько тащит boost::size.
-2
Зачем класть в репу буст?
Достаточно декларировать зависимость и использовать нормальную систему сборки (i.e. CMake).
Достаточно декларировать зависимость и использовать нормальную систему сборки (i.e. CMake).
+3
Какие мегабайты кода? Вот меня всегда удивлял такой подход.
1) Размер бинарика при использовании std::array не отличается от использования boost::array. Я не в курсе, сколько процентов кода было скопипащено, но они эквивалентны.
2) >90% boost`а — это header-only библиотеки. Они не требуют никакой линковки и дополнительных зависимостей.
3) boost модульный. Что бы использовать a, вам не нужно b.
1) Размер бинарика при использовании std::array не отличается от использования boost::array. Я не в курсе, сколько процентов кода было скопипащено, но они эквивалентны.
2) >90% boost`а — это header-only библиотеки. Они не требуют никакой линковки и дополнительных зависимостей.
3) boost модульный. Что бы использовать a, вам не нужно b.
+2
компилировался сразу после загрузки из системы контроля версий
А компилятор вы в репозиторий кладёте?
+1
Ошибка хоть и детская, но совсем не очевидная, потому что:
1. Код
char arr[100]; std::cout << sizeof(arr)/sizeof(arr[0]) << std::endl;
напишет: 100
2. А вот код:
void sample(char a[100]) { std::cout << sizeof(a)/sizeof(a[0]) << std::endl; }
напишет: 4 или 8.
Хотя вроде бы обе декларации массива одинаковые. То есть, в одном случае правило эквивалентности массива и первого элемента не работает, а во втором работает. Что, конечно же, тяжелое наследие старого С.
Кстати, на днях увидел такую вот хрень:
Компилирование кода:
int sample(int& i) { i = 0; }
заканчивается с ошибкой компиляции (Оракловский C++ компилятор 2006 года на Солярисе) типа «ошибка, функция не возвращает значение.»
А вот код:
bool sample(int& i) { if (i) return false; }
компилируется вообще без ошибок и даже без предупреждений, хотя ошибка та же. Согласно стандарту, невозврат значения во всех ветках функции — undefined behavior. Но в одном случае это ошибка компиляции, а в другом — чистая компиляция, которая ведет к возрату почти всегда неправильного значения.
1. Код
char arr[100]; std::cout << sizeof(arr)/sizeof(arr[0]) << std::endl;
напишет: 100
2. А вот код:
void sample(char a[100]) { std::cout << sizeof(a)/sizeof(a[0]) << std::endl; }
напишет: 4 или 8.
Хотя вроде бы обе декларации массива одинаковые. То есть, в одном случае правило эквивалентности массива и первого элемента не работает, а во втором работает. Что, конечно же, тяжелое наследие старого С.
Кстати, на днях увидел такую вот хрень:
Компилирование кода:
int sample(int& i) { i = 0; }
заканчивается с ошибкой компиляции (Оракловский C++ компилятор 2006 года на Солярисе) типа «ошибка, функция не возвращает значение.»
А вот код:
bool sample(int& i) { if (i) return false; }
компилируется вообще без ошибок и даже без предупреждений, хотя ошибка та же. Согласно стандарту, невозврат значения во всех ветках функции — undefined behavior. Но в одном случае это ошибка компиляции, а в другом — чистая компиляция, которая ведет к возрату почти всегда неправильного значения.
+3
Согласно стандарту, невозврат значения во всех ветках функции — undefined behavior.Undefined behaviour — это вообще не о том. Вы имеете право иметь и функцию, которая иногда не возвращает значение и функцию, которая никогда его не возвращает в программе — и это будет валидная программа на C++.
Компилирование кода:Ну, то, что компилятор по неизвестной причине бузит — это дело десятое. Строго говоря никто не запрещает подобную функцию вам в программе иметь. Использовать её будет нельзя — это да. Warning'ом всё должно бы ограничиться.
int sample(int& i) { i = 0; }
заканчивается с ошибкой компиляции (Оракловский C++ компилятор 2006 года на Солярисе) типа «ошибка, функция не возвращает значение.»
bool sample(int& i) { if (i) return false; }Как это? Тут совсем другая история. Тут функцию вполне себе можно использовать без того, чтобы вызвать undefined behavior. Более того, если вспомнить правила игры, то можно понять, что это — очень даже полезная функция! Её вызовом (если она
компилируется вообще без ошибок и даже без предупреждений, хотя ошибка та же.
inline
и описана в заголовке) вы говорите, что переменная i
— никогда не может быть равна нулю. Компилятор может это с пользой использовать (хотя найти компилятор, который бы это делал мне не удалось).P.S. Warning, кстати, и gcc и clang выдают для обоих функций.
+1
Я бы кидал предупреждение вообще на любую попытку указать длину массива в объявлении функции (если массив передается не по ссылке, конечно же). Первопричина всех подобных ошибок — это именно недопонимание конструкций вида
void something( char arr[100] )
.+1
Контроль типов для конструкций вида
void f(char x[100])
не обязывает в точности контролировать тип передаваемого значения параметра функции с точностью до количества элементов. Если нужно точно char x[100];
, то следует использовать именно ссылку void g(char (& y)[100])
.+1
Что-то некорректно написано?
0
Не понятна цель комментария.
-3
Почему непонятна? Она описывает способ описания точно такой же функции, но такой, которая будет вести себя разумно: sizeof возвращает то, что должен вернуть, всякие ARRAY_SIZE тоже работают, передать туда массив «не того размера» случайно нельзя (причём это нельзя сделать даже случайно если константа-размер-массива-изменится и вы забудете библиотеку перекомпилировать) и т.д. и т.п.
Очень удобная и полезная конструкция с несколькими недостатками: во-первых она несовместима с C, во-вторых выглядит она как-то… гхм… неаппетитно.
Самая большая проблема со всеми этими контрукциями — то, что всё это взрывает систему типов так, что хочется кому-то морду набить:
Очень удобная и полезная конструкция с несколькими недостатками: во-первых она несовместима с C, во-вторых выглядит она как-то… гхм… неаппетитно.
Самая большая проблема со всеми этими контрукциями — то, что всё это взрывает систему типов так, что хочется кому-то морду набить:
typedef struct { int a, b, c; } Str;
typedef int Arr[100];
void FooStr(Str& s); // Структура передаётся по ссылке; все довольны.
void BarStr(Str s); // Структура передаётся по значению; тоже все понятно.
void FooArr(Arr& a); // Массив передаётся по ссылке; всё нормально.
void BarArr(Arr a); // Массив передаётся... передаётся массив... по сссылке?
// Причём без контроля размера?
// WTF? Нет! *W* *T* *F* ???
Не самое приятное место в C++, согласитесь.+2
Я писал, что компилятор должен кидать предупреждение как только увидит массив с длиной в заголовке, не дожидаясь пока программист применит к нему оператор sizeof. Вы же теперь объясняете, что контроль типов ничего не обязывает. То есть вы считаете, что компилятор не должен кидать предупреждения? Если да — то у вас странные аргументы. Если же вы со мной согласны — то я не понимаю зачем вы спорите.
0
Почему тогда работает этот код?:
static DDLogFlag LocalToDDLogLevelMapping[] = { DDLogFlagVerbose, DDLogFlagInfo, DDLogFlagWarning, DDLogFlagError };
if ((logLevel >= (sizeof(InternalToDDLogLevelMapping) / sizeof(DDLogLevel))))
Собственно это Objective-C, компилируется с помощью clang, но он обратно совместим с C by definition, и если я верно понял — это особенность именно языка, а не реализации. Хотелось бы решить для себя вопрос области применимости такого подхода.
static DDLogFlag LocalToDDLogLevelMapping[] = { DDLogFlagVerbose, DDLogFlagInfo, DDLogFlagWarning, DDLogFlagError };
if ((logLevel >= (sizeof(InternalToDDLogLevelMapping) / sizeof(DDLogLevel))))
Собственно это Objective-C, компилируется с помощью clang, но он обратно совместим с C by definition, и если я верно понял — это особенность именно языка, а не реализации. Хотелось бы решить для себя вопрос области применимости такого подхода.
0
Здесь sizeof() применяется к переменной, объявленной не как параметр функции, в этом случае sizeof() возвращает размер массива.
0
Да, понял. Я довольно давно использую этот подход, не то, чтобы очень часто, но случается. Обычно для полей, объявленных как static с какими-то пресетами данных, как в этом примере.
Насколько я помогу судить, дело в потере информации о типе. В каких ещё ситуациях это может произойти?
Насколько я помогу судить, дело в потере информации о типе. В каких ещё ситуациях это может произойти?
0
Таких случаев, собственно, два: передача массива как параметра функции и описание внешнего массива. Если вы описываете что-либо типа
Но опасен только случай с передачей параметров. Если попытаться сделать что-нибудь «нехорошее» с глобальной переменной, тип которой не до конца известен (скажем
extern int a[];
то вы, тем самым, говорите компилятору: у меня тут массив, но какого он размера — я не знаю, знает только тот, кто его описал. Часто употребляется со строковыми константами.Но опасен только случай с передачей параметров. Если попытаться сделать что-нибудь «нехорошее» с глобальной переменной, тип которой не до конца известен (скажем
sizeof
вызвать), то компилятор возмутится и компилировать программу откажется. А вот с параметрами функции всё наоборот — всё отлично скомпилируется, только работать не будет.0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Новое предупреждение о неверном вычислении размера массива в gcc 5.1