Pull to refresh

Comments 103

У вас есть результаты сравнения вашего продукта с такими известными продуктами, как Fortify, Checkmarx, SonarQube?

За ответом я пошёл в Google Поиск и не сказать, чтобы удивился...

Так всё правильно. std::string не массив, даже std:array не массив, но может им притворяться.

google

Так всё правильно. std::string

Изначально там был массив - потому и такой запрос. Если же перефразировать немного правильнее, и написать "контейнер", то результат будет гораздо лучше :) Беда в том, что новичку до этого ещё нужно "дойти".

Я из своей фортрановской камеры иногда подглядываю в плюсовые статьи... Сейчас честно порадовался обычаям своего заповедника ;-) Все-таки, у нас с массивами

как-то попроще

Например, вот так будет для массивов с тремя измерениями (код можно легко обобщить на n-мерные массивы):

! Объявляем трехмерные "динамические" массивы:
real, allocatable :: A(:,:,:), B(:,:,:)
integer :: n = 42
(...)
! Выделяем память (с этого момента размер массивов
! и диапазоны изменения индексов зафиксированы):
allocate (A(1:n, 4, 3), B(0:n-1,41:44,1:3))
! Передаем их в какую-нибудь процедуру:
call myfunc(A,B)
.......................
.......................

! А это где-то внутри myfunc(A,B):
...
integer :: Ashape(3), Bshape(3), dShape(3)
integer :: A_min_index(3), B_min_index(3)
Ashape=shape(A); Bshape=shape(B)
dShape=Ashape-Bshape
A_min_index=lbound(A); B_min_index=lbound(B)

! теперь вектора (массивы) Ashape и Bshape
! содержат размер массивов A и B
! по каждому измерению (т.е.[42,4,3]),
! dShape хранит различия в размерах массивов,
! а A_min_index и B_min_index хранят
! нижнюю границу каждого индекса
! Например, B_min_index=[0,41,1]
(...)
! поэлементное умножение:

if (maxval(abs(dShape)) == 0) A=B*13


! но только ЕСЛИ все три размера у массивов
! одинаковые (модули разностей = 0).
! Диапазоны индексов при этом могут не совпадать -
! главное, чтобы размерчики совпадали

...

Ну и отдельным бонусом есть ключ компилятора, который включает проверку, что массив B инициализирован, либо выключает ее (если программа хорошо отлажена и очень торопится, это ее немного ускорит). Кстати, если проверка выключена, а значения каких-то элементов в массиве B не присвоены, то с помощью A=B*13 у нас в фортране тоже можно добиться UB!

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

Скрытый текст

subroutine show_arr(arr) integer, intent(in) :: arr(:) print *, arr end subroutine show_arr

Блок кода типа MatLab

subroutine show_arr(arr)
  integer, intent(in) :: arr(:)

  print *, arr
end subroutine show_arr

Лучше использовать специальный блок для исходного кода.

Я бы не сказал... когда доходило до common blocks. Хотя после 2001го я на Фортране не писал, что-нибудь поменялось?

Я бы не сказал... когда доходило до common blocks

;-)

Вы правы, конечно!

Разумеется, любой по-древнему хороший язык программирования должен уметь стрелять себе в ногу очередью из граблей ;-)) Более того, заложенная в фортран совместимость со старым кодом прямо требует, чтобы у программиста было такое право (в теории, наш код из 1956г все еще до сих пор обязан компилироваться и работать ;-)

Но на практике так уже никто не пишет сейчас. Вместо этого есть понятие модуля, где все переменные и структуры описываются один раз, и затем они по необходимости используются в разных местах (со стандартными в современных языках средствами ограничения видимости). Причем, оператор USE сразу оказался намного удобнее всяких includ-ов, так как он автоматически контролирует, чтобы блок включался (и компилировался) ровно один раз (привет, $IF DEFINED ;-). Одно только это обстоятельство было для многих фортранщиков достаточным аргументом сменить стиль, даже еще ничего не понимая в ООП. А еще сейчас принято методы работы с модульными данными тоже писать в тех же модулях. Фактически получаются те же объекты-классы, только с привычным для первобытных людей интерфейсом.

С учетом этого, теперь программисту на фортране необходимо достичь определенного уровня квалификации, чтобы прострелить именно свою ногу, а не ногу соседа ;-)

А вообще, язык, конечно стал заметно другим даже не в плане спецификаций, а скорее по стилю. Например, я знаю только фортран, и вроде бы знаю довольно неплохо. Но прочитать старую (из 1980-х гг) программу на фортране мне порой намного сложнее, чем аналогичную современную (=адекватно оформленную) программу на незнакомом мне (и весьма непростом!) С++. Просто потому, что тот старый фортран порой по читаемости напоминает Brainfuck. Уж не знаю, умышленно, или нет...

std::size хорош, но для многомерных массивов всё равно приходится городить огород.

Если есть свежий компилятор с поддержкой С++23, то можно использовать std::mdspan.

интересно, а если его переделать в рекусию для случая с массивами... Да только Си++23 превращение многомерного массива в одномерный является UB.

опытный огородник найдёт себе сложности )

Для них есть std::extent() .

Вы мой спаситель! Вот реально, сколько ж всего уже есть в языке, про что узнаёшь внезапно.

Что заявлено в заголовке? Как не надо проверять размер массива в С++

О чем на самом деле пишет автор? О чем угодно, только не о массивах в С++ и их размерах.

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

Я думаю, что отвел достаточную часть статьи на массивы, их собратьев и вычисление размеров в каждом случае. Не могли бы вы написать чего именно не хватило?

Если вопрос конкретно к заголовку "Как не надо проверять размер массива в С++", то ответ идёт через всю статью - используйте современные средства вместо sizeof((expr)) / sizeof((expr)[0]). Варианты на замену я также расписал в статье.

контейнер - не массив. Объект - не массив. И если уж на то пошло - надо уточнять, что такое размер массива. sizeof((expr)) / sizeof((expr)[0]) - размер в элементах, sizeof((expr)) - в байтах. В вашей же многобукве про это как-то скромно умалчивается

Более того, вы вот претендуете на не начинающего программиста

т.к. целевой аудиторией заметки являются начинающие программисты. Не будем учить их плохому :)

flame on

но почему-то таки неявно учите, что массивы в С++ - это std::xxx, а с ними нужны современные подходы, цыфровая трансформация и всякий прочий эффективный менеджмент

flame off

Здесь проблема подхода неустойчивого к рефакторингу. Когда-то в коде эта строка была фиксированным массивом и подход был легален. Массив заменили на строку, но использования не отследили. Самое смешное, что с короткими строками в некоторых реализациях это даже работает.

Хотя std::size появилось в С++17, ее аналог мусолят где-то с 1999 во всех возможных блогах, даже Мейерс об этом писал.

Здесь проблема подхода неустойчивого к рефакторингу.

ну так это совсем другое и из другой оперы - "Кто чем и о чем думает, когда что-то делает". ... тут в параллельной вселенной пишут "не думая, она кинулась"

Хотя std::size появилось в С++17, ее аналог мусолят где-то с 1999 во всех возможных блогах,

и что? Заявлены массивы, а не std:: со своими граблями

Как говорил один сотрудник, это не грабли, а плохо продуманные костыли. Увы, в языке без отражений иначе не получится. Кстати, я только сейчас заметил комментарий про функцию в Хромиуме... это код скопированный у меня одним бывшим сотрудником, ушедшим туда (с точностью до символа). А я брал идею у Мейерса. Как тесен мир.

Собственно это намеренно проталкивалость через все щели, включая буст, в стандарт. std::size, std::data и прочее. У меня были идеи как решить прблему с многомерными массивами, но фишка в том что стандарт ими не оперирует. С точки зрения стандарта многомерный массив размерности N - это одномерный массив элементов-массивов размерности N-1.И представление его как одномерного попахивает UB до Си++23.

Главное отучить народ от этой адской формулы деления.

Как говорил один сотрудник, это не грабли, а плохо продуманные костыли.

ну, в общем (не только std::) случае может быть все что угодно

Как тесен мир.

аааааааааааааааааааа....

В вашей же многобукве про это как-то скромно умалчивается

Действительно, есть такое упущение...

но почему-то таки неявно учите, что массивы в С++ - это std::xxx, а с ними нужны современные подходы, цыфровая трансформация и всякий прочий эффективный менеджмент

Не совсем так. Заманчиво просто советовать использовать средства стандартной библиотеки везде и вся, но я понимаю, что ситуации бывают разные - потому в конце есть целый раздел с подборкой вариантов в зависимости от ситуации.

В целом, не вижу ничего плохого в том чтобы новички изучали std контейнеры до build-in массивов. Да, получается не hardcore-программирование, но благодаря этому можно будет перестать изобретать неудачные велосипеды вместо стандартных реализаций. Набрался опыта - добро пожаловать на уровень "глубже".

Пока писал ответ, вспомнилась неудачная реализация кастомной функции конкатенации строк из LFortran. Мой коллега даже написал про тот случай отдельную статью.

неявно учите, что массивы в С++ - это std::xxx

Если начинающие программисты будут думать что массивы в C++ это std::array и std::vector, а T[] это такая штука из далекого прошлого примерно как register или std::auto_ptr, то всем будет только лучше.

Будет лучше ровно до того момента, когда эти программисты начнут писать std::vector<std::vector<std::vector...>>> для многомерных.

Но ведь работает же...

А вообще там мультиспаны всякие и это всяко лучше чем T*** = malloc/new T**(10)

В JDK используют sizeof(methods)/sizeof(methods[0]) и не жалуются.

https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libjava/VM.c#L51

А это — ничего, что файл, на который вы ссылаетесь, называется VM.c?
Я имею ввиду расширение файла, — то, что после точки в имени.
Может в этом файле использоваться std::size?

Они ещё и не очень-то владеют языком C.
Используют явное приведение к указателю на void при инициализации структуры JNINativeMethod:

static JNINativeMethod methods[] = {
    {"getNanoTimeAdjustment", "(J)J", (void *)&JVM_GetNanoTimeAdjustment}
};

Структура выглядит так:

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

Последнее поле имеет тип указатель на void.
Поэтому третий инициализатор не требуется приводить явно.
Этот случай, в отличие от приведения в обратную сторону, актуален даже для C++.

Сколько еще таких ошибок в коде JDK найдет ваш анализатор?

Вообще-то, это — не ошибка, особенно в C, где по-другому — никак.

Я имею ввиду расширение файла, — то, что после точки в имени.

Ну если вам важно только расширение, то можно посмотреть тут

https://github.com/openjdk/jdk/blob/master/src/hotspot/share/prims/jvmtiExtensions.cpp#L199
Или тут
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/cds/filemap.cpp#L1494

А можно запретить им использовать .h и std:: одновременно, как тут

https://github.com/openjdk/jdk/blob/master/src/jdk.jpackage/share/native/common/ErrorHandling.h#L60

P.S. у них там вперемешку C/C++, поэтому и интересно как с этим справляется анализатор

Ну если вам важно только расширение,

Это не мне важно, и это был намёк.

то можно посмотреть тут

Код у них в целом плохой, например, приведение в C-шном стиле:

(char*)"com.sun.hotspot.functions.IsClassUnloadingEnabled"

Вместо использования const_cast.
С потенциальной проблемой потом при попытке менять содержимое.

А можно запретить им использовать .h и std:: одновременно, как тут

Расширение .h используется как для C, так и для C++ файлов.

P.S. у них там вперемешку C/C++, поэтому и интересно как с этим справляется анализатор

Очевидно, что анализатор ориентируется по расширению файла.
Как видите, это не мне важно расширение файла.

Очевидно, что анализатор ориентируется по расширению файла.

Только в качестве fallback стратегии. Гораздо лучше получать такую информацию из команд компиляции

Только в качестве fallback стратегии.

Я бы не был столь категоричен.

Гораздо лучше получать такую информацию из команд компиляции

Провожу эксперимент:

$ cat > 1.cpp
#include <cstdlib>
#include <iostream>

int main() {
	std::cout << "Yes!" << std::endl; 
	return EXIT_SUCCESS;
}
$ gcc -c 1.cpp
$ 

Интересно, правда?
В строке компиляции, вроде как, должен быть C, а не C++, если судить по имени команды.

Удаётся с'link'овать и исполнить:

$ g++ 1.o
$ ./a.out
Yes!
$ 

Работает.

Хорошо, перекладываю 1.cpp в 1.c и повторяю:

$ cp 1.cpp 1.c
$ gcc -c 1.c
1.c:1:10: fatal error: cstdlib: No such file or directory
    1 | #include <cstdlib>
      |          ^~~~~~~~~
compilation terminated.
$ 

Видно, что сам gcc определяет язык по расширению файла.
А из команд компиляции такую информацию здесь достать нельзя.
Имя команды компиляции тоже не даёт однозначного ответа.

Поэтому нельзя однозначно сказать, что лучше, а что хуже.

нельзя однозначно сказать, что лучше, а что хуже

Вы совершенно правы. Добиться успеха можно только комбинируя методы. В дополнение к вашему примеру оставлю вариант, где без флажков не разобраться:

// main.cpp
void myFunc(){}
int main()
{
    myFunc(1,2,3); // Illegal in C++, but ok in C before C23
    return 12;
}

Напишем простой код, который при этом не скомпилируется в режиме С++. Положим всё это в main.cpp и соберем плюсовым компилятором в режиме Си (флаг -x c):

$ g++ -x c main.cpp 
$ ./a.out 
$ echo $? 
12

Тоже работает. Если -x c не указывать, то получим ожидаемое:

$ g++ main.cpp 
main.cpp: In function ‘int main()’:
main.cpp:5:11: error: too many arguments to function ‘void myFunc()’
    5 |     myFunc(1,2,3); // Illegal in C++, but ok in C before C23

P.S. Если интересно почему программа вообще собирается на Си, то у нас в документации на диагностику V1107 есть описание (она как раз ловит такие случаи).

Напишем простой код, который при этом не скомпилируется в режиме С++. Положим всё это в main.cpp и соберем плюсовым компилятором в режиме Си (флаг -x c)

И часто такое встречается в реальности?

Всё-таки, в подавляющем большинстве случаев, именно расширение файла — наиболее надёжный признак, на каком языке написано его содержимое.

P.S. Если интересно почему программа вообще собирается на Си, то у нас в документации на диагностику V1107 есть описание (она как раз ловит такие случаи).

Я — в курсе.
И также в курсе, что, начиная с C23, поведение изменено аналогично оному в C++.

И часто такое встречается в реальности?

Достаточно часто чтобы не пренебрегать. Конкретно анализатор без флажков компиляции жить не может, т.к. в них передается информация, без которой не провести полноценное препроцессирование и анализ.

Достаточно часто чтобы не пренебрегать.

Это — не ответ.
Здесь необходимо взять N проектов и просто проверить.
И убедиться.

Конкретно анализатор без флажков компиляции жить не может, т.к. в них передается информация, без которой не провести полноценное препроцессирование и анализ.

Анализатор — без сомнения.
Хотя бы, чтобы правильно обработать директивы условной трансляции, потому что, к примеру, в командной строке могут передаваться макро-определения, от чего будет зависеть выбор соответствующих частей кода директивами условной трансляции.

Но речь-то была о надёжности определения языка по расширению файла. И эта надёжность очень высока.

Здесь необходимо взять N проектов и просто проверить

Честно говоря, полезность и целесообразность исследования вызывает вопросы.

И эта надёжность очень высока.

Высока, но не достаточна. Для написания скриптов на коленке подойдет, но для чего-то серьёзного уже придется поддерживать и чуть-более сложные варианты.

Честно говоря, полезность и целесообразность исследования вызывает вопросы.

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

Можно и не проводить.
Но тогда на вопрос не ответить.

Высока, но не достаточна. Для написания скриптов на коленке подойдет, но для чего-то серьёзного уже придется поддерживать и чуть-более сложные варианты.

Я не говорю, что серьёзный инструмент не должен учитывать вероятность использования расширения файла не по назначению.

Однако, возвращаясь к начальному моменту, с которого всё и началось: если в проекте, например, в JDK, который, в основном, написан на C++, некий файл, например, VM.c, имеет расширение .c, то надёжность предположения, что там находится код на C, а не на C++, очень высока.

Вот, я о чём.

Сколько еще таких ошибок в коде JDK найдет ваш анализатор?

В прошлый раз смотрели в 2016 году. Хорошая идея перепроверить.

Тут не очень понятно, для кого статья. Новички сразу заюзают нужную функцию из библиотеки. Кто хотел, уже переучился. Ну а остальные необучаемые мамонты из принципа будут писать sizeof)

Новички сразу заюзают нужную функцию из библиотеки

Если бы всё было так просто... Про тот-же std::size ещё нужно знать (если вы про эту функцию).

Несколько лет назад, в моём университе, всё ещё преподавали C++ как Си с классами со всеми вытекающими. Однокурсники просто не знали, что можно реализовать многие вещи гораздо проще / удобнее / безопаснее. Эта статья как раз написана в надежде, что интересующийся человек увидит современные варианты работы с плюсами вместо очередной копипасты про sizeof.

Несколько лет назад, в моём университе, всё ещё преподавали C++ как Си с классами со всеми вытекающими. Однокурсники просто не знали, что можно реализовать многие вещи гораздо проще / удобнее / безопаснее

очень просто и удобно навернуться в эксепшн еще до main(), особенно в коде, который везет какую-нить бадью с расплавленным металлом или еще где...

В коде, который везёт бадью с расплавленным металлом, скорее всего, будут запрещены исключения, динамические аллокации памяти, RTTI и т.д. Написание насколько критичного ПО это целый отдельный мир с кучей ограничений, стандартов безопасности, аудитов и т.п. Не думаю, что целесообразно обсуждать данную область в рамках этой статьи.

Так в плюсах исключения и rtti неотключаемые по стандарту:)

Эти запреты не помогут в вышеуказанной ситуации. Особенно если писатель кода будет не в курсе чем отличается Си с классами, от плюсов на всю голову. Насколько критично ПО в случае с бадьей выяснилось после того как она подъехала к ящику с системой управления бадьей и вылила все содержимое на нее. Правда это было давно и я лично процесс не видел. Рамки статьи определяются заголовком, а заголовок ну ооочень обобщающий. А конкретное - по поводу преподавания в вашем университете. Между миром х--х-и в продакшен и там, где куча ограничений и стандартов безопасности не такая уж и большая разница и нет четкой границы

вайтишник пойдёт на стэковерфлоу и увидит самый простой вариант. крайне ленивый вайтишник спросит у чатгпт и скорее всего получит правильный ответ. про непризнающих прогресс преподов см. пункт про мамонтов (вообще не понимаю, зачем этим людям плюсы). про не умеющих ничего искать однокурсников - отдельная песня. помнится, в моей группе один чувак (будущий электромеханик) ГЕНЕРИЛ курсачи для программистов на нашем факультете (это одновременно про уровень преподов и студентов). Так что не путайте тех, кто пришёл просто за коркой, с теми, кто будет реально что-то писать.

Ну и хотел добавить, что использование сырых массивов в современных плюсах - это само по себе редкое явление... ну если использующий - не тот самый принципиальный мамонт. А брать размер таких массивов - ну вообще не понятно, зачем ))

крайне ленивый вайтишник спросит у чатгпт и скорее всего получит правильный ответ.

Скорее всего, нет: ссылка.
Там код криво отображается, однако, если его выделить, то он становится виден.

И даже если заставлять его перегенерировать ответ, станет ясно, что он сам не знает, что лучше, и в конце все равно даёт совет использовать sizeof для статических массивов: ссылка.

Ну и хотел добавить, что использование сырых массивов в современных плюсах - это само по себе редкое явление... ну если использующий - не тот самый принципиальный мамонт. А брать размер таких массивов - ну вообще не понятно, зачем ))

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

а давайте мы изображения обрабатывать без сырых массивов

Скорее всего, использовать именно сырые массивы и указатели вас будет вынуждать какая-нибудь библиотека с Си интерфейсом. Вне её зоны ответсвенности вполне можно использовать контейнеры и различные *view.

рука-лицо. Покажите как вы с этой лабудой выполняете элементарную операцию типа фильтрации окном 3x3 изображения 102x1024 и на сколько все это будет тормознее по сравнению с классическим подходом, без библиотек

К сожалению, я не компетентен в заданной вами области. Поэтому не могу конкретно сказать, насколько использование какого-нибудь std::span, вместо сырых массивов, замедлит те или иные операции в обработке изображений. Если у вас есть достоверные бенчмарки - буду рад ознакомиться. Информации на эту тему в сети не много, а потому вы могли бы написать интересную статью на эту тему.

Если вы думаете что я топлю за запрет сырых указателей, built-in массивов и прочего, то вовсе нет. Мне бы тогда пришлось бросить embedded-разработку :) В общем случае я предпочту абстракции, но если уж дело дошло до оптимизаций, то тут все средства хороши. И я очень рад что С++ предоставляет настолько широкое поле для маневров.

К сожалению, я не компетентен в заданной вами области.

рука-лицо.

Уж не знаю, какой такой embedded-разработкой вы занимаетесь, но сейчас embedded вполне может быть и с обработкой изображений, начиная с его получения, с, внезапно, матрицы- двумерного массива.... В другом embedded случае совершенно внезапно используется перемножение матриц, т.е. двумерных массивов в полетном контроллере. И никто дурью не мается

Если у вас есть достоверные бенчмарки - буду рад ознакомиться. Информации на эту тему в сети не много, а потому вы могли бы написать интересную статью на эту тему.

никому в голову не приходит маяться дурью, все хорошо в своей области использования. Если вам совсем заняться нечем - перепишите хорошо известную библиотеку с контейнерами и без raw массивов. Заодно можно сделать то же самое с jpglib и pnglib а потом запилить бенчмарки..

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

Внезапно, но embedded, как и разработка в целом, не зациклена на одной только обработке изображений и операциях над матрицами.

Если вам совсем заняться нечем - перепишите хорошо известную библиотеку с контейнерами и без raw массивов.

За ссылку спасибо, а вот переписывание и эксперименты над библиотекой предпочту оставить заинтересованным лицам.

Смотря как на это посмотреть.. огромное количество задачь реализуется через работу с матрицами и изображениями, не только в эмбеддед, даже если они не связаны. Вся сложная криптография, например. обработка сигналов - это матрицы, матрицы и еще раз матрицв. Одна из бед этой сферы необходимость оптимизироваться под потоковые опервции (SIMD, SSE, NEON и т.д.) где абстракции границ объектов будут только удалять гарантиии... вот пишется либо на Си, либо С++. Одна здакомая проводила сравнение на примере спектрального анализа на андроидедля Software Defined Radio: Java в 20 медленнее чем нативный "высокородный" C++, а тот в 3-5 раз медленнее чем C++\C с явно использованнимы функциями NEON

Внезапно, но embedded, как и разработка в целом, не зациклена на одной только обработке изображений и операциях над матрицами.

да ради бога. Совсем типовая задача embeded - посылать/принимать массивы/структуры данных. Хочешь - через tcp/udp, хочешь через uart или spi. В упомянутой рядом жабе это делается аналогично удалению гланд через задний проход, с соответствующими затратами ресурсов.

За ссылку спасибо, а вот переписывание и эксперименты над библиотекой предпочту оставить заинтересованным лицам.

библиотека используется вероятно на каждом сайте (и не только), а переписывать ради религиозных заблуждений никто не будет. Еще раз, каждому методу и подходу - свое место. Как лучше/производительнее/понятнее/переносимее - так и делай. Хочешь - пихай массивы внутрь контейнеров, хочешь - делай массивы контейнеров, но не надо поклоняться и возводить в абсолют один подход.

Ваша libgd была написана 30 лет назад и на Си. Разумеется, что просто так что-то пеерписывать с одного на другое никто не будет (да и смысла не много), но речь-то вообще не про это.

Когда кто-то в контексте плюсов говорит: "использование сырых массивов (или указателей, или ручные new-delete, и т. д., и т. п.) — редкое явление" — то не подразумевает, что это это плохо. Подразумевается, что плюсовый подход это локализовать все эти вещи в одном месте, и покрыть более-менее высокоуровневым интерфейсом.

редкое оно для тех, кто не пользуется.

Подразумевается, что плюсовый подход это локализовать все эти вещи в одном месте

это не плюсовой подход - это плюсовой на всю голову, ради религии, без учета соображений производительности, удобночитаемости, понимаемости кода, переносимости, безопасности и т.д.

Никто не запрещает инкапсулировать и локализовать в одном месте работу с массивами и матрицами в тупо Си-стиле

без учета соображений производительности

Какие ваши доказательство того, что, если я, например, засуну все проверки/аллокации/копии/деаллокации в функцию resize, то это будет менее производительно чем ручной вызов всей этой мишуры?

удобночитаемости

resize удобочитаемее проверки/аллокации/копии/деаллокации, я гарантирую это.

понимаемости кода

resize понимаемее проверки/аллокации/копии/деаллокации, я гарантирую это.

переносимости

проверки/аллокации/копии/деаллокации внутри resize более переносимы чем таковые вне уже из-за того, что это всё локализовано.

безопасности

проверки/аллокации/копии/деаллокации внутри resize настолько же безопасны, насколько они безопасны вне.

Никто не запрещает инкапсулировать и локализовать в одном месте работу с массивами и матрицами в тупо Си-стиле

Никто и не против. Речь про то, чтобы это, образно, снаружи выглядело как void some_container::copy_from(some_container &src), а не как void copy(void *src, void *dst, size_t size).

Какие ваши доказательство того, что, если я, например, засуну все проверки/аллокации/копии/деаллокации в функцию resize, то это будет менее производительно чем ручной вызов всей этой мишуры?

более производительно будет не использовать всё это. И менее кучезасирательно.

resize понимаемее проверки/аллокации/копии/деаллокации, я гарантирую это.

Вы гарантируете, что когда видите A = B; то это именно присваивание, а не неведомая фигня, которая тянет за собой 100500 вызовов и конструкторов? Особенно сексуально на это смотреть в каком-нибудь отладчике, стоя раком под/над каким-нибудь устройством.

Никто и не против. Речь про то, чтобы это, образно, снаружи выглядело как void some_container::copy_from(some_container &src), а не как void copy(void *src, void *dst, size_t size).

если оно не влияет на производительность или еще что существенное

более производительно будет не использовать всё это

Какие ваши доказательства?

Вы гарантируете, что когда видите A = B; то это именно присваивание, а не неведомая фигня, которая тянет за собой 100500 вызовов и конструкторов?

Если я сам пишу этот operator=, то я могу гарантировать что угодно. Если я тягаю из какой-то библиотеки, то гарантий у меня не больше чем если я тягаю из библиотеки copy(&A, &B, sizeof(A)).

если оно не влияет на производительность или еще что существенное

Оно не влияет и никогда не влияло.

Какие ваши доказательства?

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

Если я сам пишу этот operator=, то я могу гарантировать что угодно.

опыт говорит нам, что гарантировать что либо на 100% нельзя, даже если сам. Даже самому можно поскользнуться, упасть, а потом "тут помню, тут не помню". Или каким-нить ковидом переболеть.

Оно не влияет и никогда не влияло.

опять юношеский максимализм и пионэрский задор

потому как в some_container::copy_from() можно понапихать всего чего угодно и получить вагон и маленькую тележку сайдэффектов.

.

100500 раз подтверждалось преимущество статических массивов

Преимущество в чем?

потому как в some_container::copy_from() можно понапихать всего чего угодно и получить вагон и маленькую тележку сайдэффектов

Исходя из такой логики, можно переходить сразу на Assembly. Там ещё сложнее сделать что-нибудь неявно. Да и вообще от стандартной библиотеки отказаться и все функции писать самому (со всеми вытекающими последствиями).

Преимущество в чем?

в производительности же. Вам уже было сто раз указано и на обработку изображений, и на матричные операции в контроллерах движения, 3D кадах или шуттингах и еще бог знает где. Это если не вспоминать про фрагментацию кучи, которую можно и на x64 при желании в усмерть зафрагментировать, а на более других архитектурах оно может и без желания вылезти

Исходя из такой логики, можно переходить сразу на Assembly. Там ещё сложнее сделать что-нибудь неявно.

ну так переходите, вам никто не запрещает. Только не абсолютизируйте свой локальный опыт на всё.

Плюсовые контейнеры и динамическое выделение памяти вещи ортогональные. Менеджментом памяти занимается аллокатор, а не контейнер, и аллокатор для всех плюсовых контейнеров (за вычестом нескольких строго статических) кастомизируемый. Скормите контейнеру любой угодный вам аллокатор и управляйте памятью как вам хочется. А пока ваши доводы звучат примерно как: "я бьюсь головой об стену и мне больно." Ну так не бейтесь :)

что либо на 100% нельзя, даже если сам

В таком случае и ваша православная библиотека на Си тоже на 100% гарантий не даёт.

потому как в some_container::copy_from() можно понапихать всего чего угодно и получить вагон и маленькую тележку сайдэффектов.

А в copy(void *src, void *dst, size_t size) нельзя, я правильно вас понял?

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

А в copy(void *src, void *dst, size_t size) нельзя, я правильно вас понял?

можно. Но возможностей для приглючений там значительно меньше

std::vector это глорифайд маллок с фри в конце (который не про***ть) и оверхеды есть только в дебаг билдах.

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

Ну а вообще, вам какая разница при ваших многоматричных хитрых операциях работаете вы с

`char* data = malloc(1024)`

или с

`auto* data = data_vec.data()`

Просто со вторым утечь не получится.

при использовании статических массивов никакой маллок не используется напрочь.

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

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

и оверхеды есть только в дебаг билдах.

если вы заметили оверхед в дебаг билде на тесте, то в релизе на реальных данных с большой долей вероятности оно тоже вылезет. Все зависит от решаемой задачи.

сразу видно диванного теоретика

Я понял, вы тут только понабрасывать, на все вопросы стрелки переводите на оппонента.

Попробуйте ну хотя бы потаскать иконку указателя мышки по экрану с этими вашими контейнерами и аллокаторами без вызова системных функций

Я искренне не понимаю, с чем вы спорите? Если вам не нужны динамические массивы, вы их не используете. std::vector понятия не имеет откуда у него там память, вставьте свой аллокатор без кучи и радуйтесь жизни.

Но возможностей для приглючений там значительно меньше

С чего бы это? Нет абсолютно, подчёркиваю, абсолютно ничего такого, что может произвойти в some_container::copy_from и чего не может произойти в copy(void *src, void *dst, size_t size).

void copy(voif *src, void *dst, size_t src)
{
    do_actual_copy(src, dst, src);
    send_nudes_to_bosses_wife();
    annihilate_all_puppies();
    write_fuck_you_to_stdout();
    do_rm_rf();
}

Я понял, вы тут только понабрасывать, на все вопросы стрелки переводите на оппонента.

еще раз - смотрим на заголовок статьи и читаем содержимое.

Я искренне не понимаю, с чем вы спорите? Если вам не нужны динамические массивы, вы их не используете

это вы расскажите автору и прочим отрицателям старорежимных массивов.

std::vector понятия не имеет откуда у него там память, вставьте свой аллокатор без кучи и радуйтесь жизни.

какой хрен разница под каким соусом вызывать memcpy()? Ну кучу засирать не будет, зато скармливать аллокатору нужно что? Статический массив. Против религии же.

С чего бы это? Нет абсолютно, подчёркиваю, абсолютно ничего такого, что может произвойти в some_container::copy_from и чего не может произойти в copy(void *src, void *dst, size_t size).

Но вы же сами явно написали, да еще и в соответствии с традицией и правилами хорошего тона, что оно там внутри делает. В соответствии с теми же правилами хорошего тона в void copy() не делается никакого безобразия и делается только заявленное copy. Кроме того, функционал copy не меняется от изменений вне тела copy (как правило). А с этим вашим ООП хороший тон нифига не помогает и получаются нежданчики, когда меняешь в одном месте, а вылазит в совершенно неожиданном. Одно неловкое движение - и ты - отец, мать, сын и тёща сразу.

еще раз - смотрим на заголовок статьи и читаем содержимое.

Всё ещё не могу понять суть проблемы. В заголовке "Как не надо проверять размер", в статье - "не используйте sizeof вариант". Старый метод раскритиковал, предложил варианты решения. Или я что-то упускаю?

для вас тоже нет разницы между сущностями языка массив и класс?

Конечно разница есть. Только не пойму при чем тут этот вопрос. В статье я, в основном, рассказывал про std::size, а он умеет как в массивы, так и в подходящие классы.

В заголовке нет std:size но есть массивы. В статье нет массивов в терминах языка C++, но есть std который в терминах C++ есть класс.

ну да, надо было написать std:xxx ваше

std::size тоже не класс, а функция.

Нет, функция класса описывалась бы как std::class_name::size, как, например, здесь: https://en.cppreference.com/w/cpp/container/vector/size

std::size это именно свободная функция, которая, в том числе, умеет принимать обычные массивы.

В статье нет массивов в терминах языка C++

Если уж настолько детально, то в примере под названием "У меня обычный массив" как раз красуется статический built-in массив и то, как свободная функция std::size вытаскивает из него размер.

В моей практике для таких задач применялись библиотеки типа Eigen, которые несмотря на использование классов и прочих преимуществ (современных) плюсов, скорее всего будут быстрее чем код, написанный "классическим подходом, без библиотек".

быстрее и удобнее в использовании - да, быстрее - маловероятно, потому как там аналитически считается количество сложений и умножений с плавающей точной на одну матричную операцию, а оно хоть на фортране, хоть на це с плюсами или без плюсов одинаково. Оверхед за счет насилования кучи возникает бездумном использовании классов и нежелании использовать static вместо new.

Я говорил именно о скорости исполнения (скорость и удобство написания кода само собой разумеются). Там небольшие матрицы на стеке размещаются. И все операции по возможности через simd инструкции оптимизируются. Использование классов и размещение объектов в куче это вообще ортогональные вещи.

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

ну и ssid инструкции не во всех архитектурах есть

вот если в регистрах, то есть разница.

А с точки зрения языка разницы нет. Компилятор может поместить переменную на стеке, а может сразу в регистре, а может вообще нигде не размещать, а полностью соптимизировать. Указать напрямую вы ему не можете, и от того, используются ли классы, это опять же не зависит.

тогда преимущество конкретной библиотеки будет в том, что ее сделали эти боги

Скорее в том, что это сделали один раз для всех, и программист, который хочет просто сделать фильтрацию изображения окном 3x3, может не вникать в микрооптимизации.

В случае ручного написания С кода можно код наподобие
image = np.clip(127.5 * (image + 1.0), 0.0, 255.0).astype(np.uint8).transpose(1, 2, 0)
уложить в 1 цикл, минимизировав обращения к памяти. Сможет ли C++ компилятор так сделать, или разобьёт на 5 проходов?

В случае ручного написания С кода можно код наподобие ... уложить в 1 цикл, минимизировав обращения к памяти

Без примера такой реализации сложно сказать. Скорее всего, реализация на Си без проблем скомпилируется и на С++. Дальше останется только сравнивать ассемблерный вывод.

Во-первых, никто не мешает и на C++ с библиотеками написать явно цикл. Во-вторых, библиотеки OpenCV и Eigen ленивые и цепочку операций до приведения к результирующему массиву или матрице сворачивают в один цикл (см. OpenCV, Eigen).

Большое спасибо за такую интересную статью!

Я читала статью как начинающий программист, прошедший половину базового курса ООП на плюсах: как будто немного не хватает уточнений по ходу текста. Используются термины, но не поясняется, в чем между ними разница. Например, как я в итоге поняла из статьи, проблема sizeof() в том, что она считает разницу между указателем на начало и на конец. Но в абзаце это сформулировано не очень явно. Было бы здорово, наверное, добавить какое-то краткое обобщение абзаца в конце с простыми пояснениями (у нас есть вот это, но оно работает вот так — поэтому нам это не подходит в современной жизни).

Дальше по тексту начинается упоминание и перечисление разных структур. Разницу между некоторыми я знаю на опыте, но в целом я восприняла это как отрывок разговора между опытными программистами на кухне — что-то краем уха удалось уловить и осознать, но в целом лавина из терминов. Могу предложить, что здесь помогла бы более резкая структуризация текста, чтобы разделить переход от обсуждения использования функций вычисления размера одних типов от других.

Примеры и ссылки очень познавательные. В целом прочитать статью было одно удовольствие. Большое спасибо! :)

Используйте std контейнеры (почти всегда std::vector, где надо - std::unordered_map/unordered_set), а массивы и ручное придумывание себе головняка оставьте свидетелям проблем в стд.

Проблема sizeof в том, что им очень просто выстрелить в указатель, сделать не то, что задумано, вообще не получить толкового результата, а все от того, что кому-то лень узнать про существование массива в котором уже лежит сайз-капасити (вектор) и не городить головняк с узнаванием размера постфактум.

Хотя честно говоря std::array, не то чтобы очень удобен в использовании. Постоянно упираешься в то, что он фикс длинны и надо городить вокруг него зоопарк с size/capacity чтобы красиво заполнять данными. Вот бы заехал SSO вариант вектора в стандарт, ух зажили бы...

Нужно еще не забыть что size и capacity - разные вещи. Да и оригинал кода писался еще в страхе лишних выделений памяти -SSO тогда не было. Хотя нынче на некоторых реализациях строка в 32 символа в SSO не влезает.

А еще некоторые люди боятся этих классов потому что "их нет в документации и они - сторонняя библиотека". Это результат методики обучения в ВУЗах где под страхом топора нельзя ими пользоваться.

Я читала статью как начинающий программист, прошедший половину базового курса ООП на плюсах: как будто немного не хватает уточнений по ходу текста. Используются термины, но не поясняется, в чем между ними разница.

Уровень сложности статьи заявлен как "Средний", видимо, этот уровень у вас ещё впереди.

Например, как я в итоге поняла из статьи, проблема sizeof() в том, что она считает разницу между указателем на начало и на конец.

Нет, операция sizeof не считает разницу между указателем на начало и конец.
И это — не функция, а операция.

Проблема состоит в том, что в выражении sizeof(a) / sizeof(a[0]) в качестве a может выступать не только массив, и при этом никаких ошибок компиляции и даже предупреждений не будет, вместо этого будет неправильно вычисленное значение.

В качестве a может выступать, например, указатель или тип, для которого определён operator [], возвращающий подходящий тип: в этих случаях выражение sizeof(a) / sizeof(a[0]) валидно, и поэтому нет ни ошибки компиляции, ни предупреждения, но результат, тем не менее, почти наверняка, — неверен.

Использование шаблонной функции std::size() вместо выражения sizeof(a) / sizeof(a[0]) решает эту проблему. Добавление специализаций этой функции решает дополнительные проблемы подобного рода.

Было бы здорово, наверное, добавить какое-то краткое обобщение абзаца в конце с простыми пояснениями (у нас есть вот это, но оно работает вот так — поэтому нам это не подходит в современной жизни).

Для этого есть комментарии, если, конечно, найдётся кто-то, кто кратко объяснит суть дела.

Спасибо за отзыв и подмеченные недостатки. Постраюсь учесть и избежать их в будущих публикациях :)

На остальные вопросы, вроде, уже успели ответить неравнодушные комментаторы.

Всякая функция уместна на своем месте.

Очевидным плюсом этой функции является то, что при попытке подсунуть ей неподходящий тип или указатель, мы получим ошибку компиляции.

А если у подсунутого типа перегружен оператор []... Мы получим ошибку компиляции, или результат будет подобен std::string и классическому делению?

А если у подсунутого типа перегружен оператор []... Мы получим ошибку компиляции, или результат будет подобен std::string и классическому делению?

Если посмотреть справочник, максимально близкий к первоисточнику, то выяснится, что шаблонная функция std::size() вызывает метод size() у переданного объекта, и имеется лишь только одна специализация std::size() для массивов.

Никакого отношения к наличию или отсутствию operator [] эта шаблонная функция не имеет.

Однако, сказано:

Custom overloads of size may be provided for classes and enumerations that do not expose a suitable size() member function, yet can be detected.

То есть, вопрос не в наличии или отсутствии operator [], а в наличии или отсутствии подходящего метода size() у объекта.

При этом, если подходящего метода нет, допустимо добавить свою специализацию шаблонной функции std:size(), и тогда std::size() начнёт работать правильно и для этого типа. В этом сила свободной функции по сравнению с вызовом метода.

Вот пример.

У struct one вообще нет метода size().
А у struct two он есть, но возвращает совсем не то, что надо.
Но это решается добавлением соответствующих специализаций std::size().
После раскомментирования #if 0 в двух местах всё начинает работать правильно.

Именно по этой причине следует предпочитать использованию методов использование свободных (шаблонных) функций std::begin(), std::end(), std::size(), std::data() и так далее.

¿А, то есть для стандартных массивов она не делает sizeof/sizeof[]?

Понятно, спасибо!

¿А, то есть для стандартных массивов она не делает sizeof/sizeof[]?

Да, верно.

В этом справочнике, который я настоятельно рекомендую, поскольку он, фактически, содержит выжимки из стандартов, а стандарт это — последняя инстанция, для std::size() есть раздел Possible Implementation, в котором есть часть size (3).

Вот эту реализацию можно попробовать самому и убедиться, что она работает, причём функция является constexpr, то есть, в тех случаях, когда это возможно, её значение может быть вычислено и использовано прямо на этапе компиляции.

Вот пример, в котором количество элементов массива a1 задаётся с помощью вызова функции std::size() для массива a0, а количество элементов массива a2 задаётся с помощью вызова функции my::size() для массива a1, которая взята из того самого раздела Possible Implementation, и всё прекрасно компилируется, ни один компилятор даже ни пикнул.

Логично, можно же шаблонное раскрытие взять. Спасибо!

И реализация (с поправками) работала в самом древнейшем компиляторе, я сто лет пользуюсь. VC6? gcc3? без проблем. Не знаю почему он знаковую версию только сейчас содрали.

У меня первым результатом вылезно https://ru.stackoverflow.com/questions/578109/Как-узнать-размер-массива-переданного-в-функцию

Повезло... и Гугл.

Ну может, потому что я - мод на стеке.

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

Яндекс не нашел ничего приличного... вернее выдал Си-шарп почему-то.

Бинг нашел MSDN

В нашем блоге есть англоязычная версия данной статьи - для неё я сделал запрос и скриншот на английском. Результат +/- тот-же, только на сайт другой указывает.

Зависит от того, что искать. я часто ищу по Qt и бусту. Ну и да, профиль обучения поисковика у вас может быть получше

Попробовал запросы из обеих версий, на английском результат в точности как у вас, на русском плашки или ответа в выделенном тексте нет, зато есть блок с похожими вопросами, и там "как узнать длину массива в C++" с ответом про sizeof. Упомянутый результат со стековерфлоу тоже есть, но ниже этого блока.

У меня есть только один указатель (например, создали массив через new)

В большинстве случаев придётся немного переписать программу и добавить передачу размера массива. Увы.

не "увы", а "и правильно" ) и лучше не просто размер передавать а использовать STL контейнер или собственный, если STL по каким-то причинам не устраивает.

Sign up to leave a comment.