1. C++ / Говнокод #25991

    0

    1. 1
    2. 2
    3. 3
    4. 4
    char (&getArray())[11] {
      static char arr[] = "1234567890";
      return arr;
    }

    Как вернуть массив из функции в C/C++

    На самом деле нет: возвращается ссылка

    Запостил: Elvenfighter, 24 Октября 2019

    Комментарии (97) RSS

    • Но, кстати, работает как и ожидалось: https://cppinsights.io/s/1f526251
      Ответить
    • зачем заворачивать в лямблию, когда можно просто в струхтуру?
      Ответить
      • Где тут лямблия?
        Ответить
        • тут нет лямблии, тут есть во-первых статическое что-то, на что вероятно можно возвращать реф.
          А кроме того это что-то еще и само указывает на стат литерал, который вообщзе небось никуда не денеца
          Ответить
          • В секцию данных помещаются 11 символов:
            .ascii "1234567890\0"

            Функция возвращает ссылку на этот массив.

            На многих платформах эта секция ещё и не защищена от записи (в ту же секцию помещаются переменные с начальным значением), так что теоретически можно этот литерал испортить.
            Ответить
            • показать все, что скрытоvanished
              Ответить
            • Испортить его можно разве что неопределенным поведением. В плюсах строковые литералы это массивы const char
              Ответить
              • Стал разбираться. Если написать static char arr[] = "1234567890"; то константа помещается в секцию данных и её можно изменить:
                #include <iostream>
                
                char (&getArray())[11] {
                  static char arr[] = "1234567890";
                  return arr;
                }
                
                int main() {
                   char * stroka = getArray();
                   std::cout << stroka << std::endl;
                   stroka[0] = '9';
                   std::cout << stroka << std::endl; // выведет 9234567890
                   std::cout << getArray() << std::endl; // выведет тоже 9234567890
                   return 0;
                }

                Этот код портит значение массива static char arr[], объявленного внутри функции. Причём в ассемблерном выхлопе никакого копирования нет, портится оригинал:
                 	.data
                __ZZ8getArrayvE3arr:
                	.ascii "1234567890\0" ; это начальное значение
                	.text
                __Z8getArrayv:
                LFB1546:
                	push	ebp
                	mov 	ebp, esp
                	mov 	eax, OFFSET FLAT:__ZZ8getArrayvE3arr ; возвращаем в eax адрес статической переменной
                	pop	ebp
                	ret


                Если же написать сразу return "1234567890", то константа "1234567890\0" помещается в секцию .rdata, а не .data. Некоторые линкеры и некоторые ОС защищают такую секцию от записи, некоторые — нет. Но в любом случае с точки зрения C++ это уже будет const char, поэтому без const_cast изменить не дадут.
                Ответить
              • А теперь самый ад: gcc с ключом -fmerge-all-constants. Пишем код:
                #include <iostream>
                
                typedef char (& pituh)[11];
                
                char (&getArray())[11] {
                  return const_cast<pituh>("1234567890");
                }
                
                int main() {
                   char * stroka = getArray();
                   std::cout << stroka << std::endl;
                   stroka[0] = '9';
                   std::cout << stroka << std::endl;
                   std::cout << "1234567890" << std::endl;
                   return 0;
                }
                На моей платформе оба литерала попали в секцию .rdata, защищённую от записи, поэтому на строке stroka[0] = '9'; программа упала.

                Исправляем: gcc -S, меняем секцию с .rdata на .data (представим себе, что у нас линкер использует платформу типа a.out, где нет .rdata), собираем... литерал чудесным образом испортился.
                Ответить
      • тут фунгкция возвращает указатель на строковой литерал, что не так?
        Ответить
        • Nein! Функция возвращает ссылку на массив. Для понимания:
          int (&getArray())[4] {
            static int arr[] = {1, 2, 3, 4};
            return arr;
          }
          
          int main() {
            int sum = 0;
            for (int e : getArray())
              sum += e;
            return sum; // вернет 9 (1 + 2 + 3 + 4)
          }
          Ответить
          • Массив в данном случае является строковым литералом, не?
            Ответить
          • Реально, питушок?

            Ты строковый литерал от массива отличаешь? Иди матчасть поучи, питух.
            Ответить
          • На всякий случай напоминаю тебе, что отвечать безграмотному уёбку, который косит под superkiller1997, не нужно
            Ответить
      • > зачем заворачивать в лямблию
        там лямбд нет

        > когда можно просто в струхтуру?
        std::array так и делает.
        Ответить
        • > там лямбд нет
          Они и не нужны. Их придумали анскильные питухи, не осилившие указатели на функции.

          > std::array
          Питух, убери от меня это говно. Питух не осилил вернуть массив из функции, и придумал это говно.
          Ответить
        • показать все, что скрытоvanished
          Ответить
    • Для сравнения Free Pascal:
      type massiv = array[0..10]of char;
      
      function getArray: massiv;
      const arr: massiv = '1234567890';
      begin
        getArray := arr
      end;
      
      begin
          Writeln(getArray)
      end.
      Вызов функции getArray компилируется в такое:
      lea	esp,[esp-12] ; выделяем в стеке место под результат
      ...
      lea	eax,[ebp-12] ; передаём в функцию последним аргументом указатель на выделенный кусок памяти
      call	P$PROGRAM_$$_GETARRAY$$MASSIV
      lea	esi,[ebp-12] ; используем заполненный функцией блок данных


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

      Эквивалентный код:
      type massiv = array[0..10]of char;
      
      procedure getArray(var x: massiv);
      const arr: massiv = '1234567890';
      begin
        x := arr
      end;
      
      var y: massiv;
      begin
          getArray(y);
          Writeln(y)
      end.
      Тут явная передача по ссылке, но тут нужно явно же заводить переменную под результат, а в случае функции результат был безымянным.
      Ответить
      • показать все, что скрытоvanished
        Ответить
        • показать все, что скрытоvanished
          Ответить
        • Помню. Я даже видел его форк arjz, который умел сжимать сильнее. А ещё я помню jar, но не который используется Жабой и является зипом по сути, а другой, который был очередной попыткой замены arj, но не стал популярным.
          Ответить
        • >> а сложные агрегаты через создание их на стеке вызывающей стороны и передачи указателя

          Если завернуть в структуру, то сишка/кресты поступают так же, как Паскаль:
          #include <iostream>
          typedef struct pituh_tag {char data[11];} pituh_type;
          
          
          pituh_type getArray() {
            static pituh_type arr = {.data = "1234567890"};
            return arr;
          }
          
          int main() {
            unsigned sum = 0;
            for (auto e : getArray().data) {
              sum += e;
            }
            std::cout << sum << std::endl;
            return 0;
          }

          Ассемблерный выхлоп:
          subl	$52, %esp ; занимаем место в стеке
          ...
          leal	-36(%ebp), %eax ; передаём в функцию указатель на выделенный блок
          movl	%eax, (%esp) 
          call	__Z8getArrayv ; вызываем
          leal	-36(%ebp), %eax ; используем указатель на блок, который заполнила функция


          Если же я сделаю typedef char pituh_type[11], то компилятор ругнётся:
          error: 'getArray' declared as function returning an array
          Он согласится компилировать код, только если объявить pituh_type & getArray(), и в этом случае в eax/rax вернёт ссылку.

          Почему в сишке и в крестах так недолюбливают массивы, что их обязательно нужно завернуть в структуру, чтобы передать по значению?
          Ответить
          • показать все, что скрытоvanished
            Ответить
            • Массивы в сишке на особом положении для того, чтобы элементы массивов можно было копировать выражением *p++ = *q++, в то время как в других языках программирования было p[i] = q[i]; i = i + 1. Полезная особенность, не правда ли?
              Ответить
      • пассалист инканус серет под себя
        array[0..10]

        '1234567890'

        оно вообще сконпелицца?
        Ответить
        • Блядь, откуда я взял ассемблерный код? В уме компилировал по-твоему?

          https://ideone.com/SNuZfm

          Короче, иди в свой «Сосач» или в свои «Одноклассники». Там твоя аудитория.
          Ответить
          • показать все, что скрытоvanished
            Ответить
          • показать все, что скрытоvanished
            Ответить
            • Хорошо, постараюсь.

              Кстати, для сомневающихся: в Турбо Паскале и в его потомках массивы of char можно инициализировать строковым литералом. Если литерал короче массива, хвост добивается байтами, равными нулю. Наоборот же (если массив короче литерала) не работает.
              Ответить
              • брехливость-русни.jpg
                Ответить
              • показать все, что скрытоvanished
                Ответить
                • А как же студенты?
                  Ответить
                  • показать все, что скрытоvanished
                    Ответить
                    • показать все, что скрытоvanished
                      Ответить
                    • Причём у Стертора откуда-то Турбо Паскаль шестой версии. Я такую версию запускал пару раз в жизни: первый раз она мне попалась лет 20 назад на диске с компиляторами; второй раз запустил сейчас, чтобы проверить инициализацию массивов.

                      Самая ватная версия, кстати: у TP6 даже была кустарная русификация IDE (мне, правда, такой мутант не попадался).

                      P.S. Ещё Тарас знает.
                      Ответить
                  • показать все, что скрытоvanished
                    Ответить
                    • ABC.NET анскилльный (см. секцию «Отсутствует»):
                      http://pascalabc.net/downloads/pabcnethelp/index.htm?page=Common/PABCNETvsDelphi.html

                      И инициализации массивов строковым литералом в нём тоже нет.

                      Плохо сделали, тупо.

                      Ещё и сайт на фреймах, так что я сначала неправильную ссылку скопировал. Какой анскилл )))
                      Ответить
                      • показать все, что скрытоvanished
                        Ответить
                        • Кое-что мне даже понравилось: локальные переменные блока, локальные переменные цикла, генерики, лямбды с замыканиями (увы, замыкания неявные), методы-расширения, множества на базе произвольных типов, кортежи, срезы, yield, анонимные классы.

                          Странно, что они добавили много интересного, но при этом много интересного убрали вроде записей с вариантами и вариантных типов. И вот даже такую мелочь убрали, как инициализация массивов строковым литералом. Нужно явно писать ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0') вместо '1234567890'.
                          Ответить
                • Проверил несколько версий. Автопаддинг строковых литералов нулями появился в TP7. Во Фрипаскале и в Дельфи он тоже есть. В TP6 автопаддинга не было.
                  Ответить
          • Ну blackwater на дамбассе ты же взял откуда-то, лишнехромосомный.
            В общем, сасай.
            ­  File  Edit  Search  Run  Compile  Debug  To
            ╔═[■]════════════════════════════ NONAME00.PA
            ║ Error 100: String length mismatch.
            ║{$X-}
            ║type TLA = packed array [0..2] of Char;
            ║const LA: TLA = 'AB';
            ║
            ║begin
            ║  Writeln('''', LA, '''');
            ║end.
            ║
            ║
            ║
            ║

            Ватники даже в пассаль не могут, дно пробито!
            Ответить
    • а если вместо строки будет {1,2,3,4}, то все равно будет работать?
      а если static убрать?
      Ответить
      • 1. будет работать
        2. скомпилируется с ворнингом "returning a reference to local", возможно будет рабтать, но это неточно.
        Ответить
        • 1. пушо static?
          2. пушо строковой литерал?
          а если сделать 1 и 2?
          Ответить
          • 1. Нет, потому что char -- целочисленный тип
            2. Нет, потому что UB
            3. Будет 1 и 2
            Ответить
            • >1. Нет, потому что char -- целочисленный тип
              и что? ну я создал массив челочисленных типов на стеке, и вернул на него ссылку.
              Массив закончился в месте с функцией, разве нет?

              >2. Нет, потому что UB
              почему нельзя вернуть ссылку на статическую переменную?
              Ответить
              • > Массив закончился в месте с функцией, разве нет?
                Согласно стандарту. Что компилятор сделает -- вопрос отдельный.

                > почему нельзя вернуть ссылку на статическую переменную?
                потому, что можно, АПВС?
                Ответить
                • Постой, я запутался)

                  1. Вернуть массив по стандарту нельзя. Это UB.
                  2. Но если он статический то можно. По стандарту.
                  3. Если массив инициализрован строковым литералом, но возвращать его все равно UB. Но почти всегда это будет работать. Потому что литералы никуда не деваются. Но могут. Но не деваются.

                  Верно?
                  Ответить
                  • > 1. Вернуть массив по стандарту нельзя. Это UB.
                    Нет, просто нельзя. Не скомпилируется: нет соответствующего синтаксиса. А в примере возвращается ссылка (читай текст под спойлером).

                    > 2. Но если он статический то можно. По стандарту.
                    В контексте №1, все так же нельзя. Но ссылку вернуть можно, и это не UB.

                    > 3. Если массив инициализрован строковым литералом, но возвращать его все равно UB. Но почти всегда это будет работать. Потому что литералы никуда не деваются. Но могут. Но не деваются.
                    Да.
                    Ответить
                    • >> 3. Если массив инициализрован строковым литералом, но возвращать его все равно UB. Но почти всегда это будет работать. Потому что литералы никуда не деваются. Но могут. Но не деваются.
                      >Да.
                      Шта?

                      char * foo()
                      {
                        //char a[] = { 1, 2, 3, 4 };
                        char a[] = "pehetut";
                        // char a[2]; a[0] = 0;
                        a[1] = '☺';
                        return a;
                      }
                      Какая разница, чем массив инициализирован, данные все равно скопируются в стек. UB.
                      Ответить
                  • >> 1. Вернуть массив по стандарту нельзя. Это UB.

                    Проверил, как ведёт себя gcc.

                    1. В C++ вернуть массив нельзя. Можно вернуть только ссылку на массив, явно указав & у типа результата функции.

                    2. В Си вернуть массив тоже нельзя, но в Си нет ссылок. Можно вернуть указатель на массив (что по сути будет указателем на указатель) либо указатель на начальный элемент массива либо завернуть массив в структуру.
                    Ответить
            • показать все, что скрытоvanished
              Ответить
    • Однопоточненько.
      Ответить
      • А если возвращаем const char, а не char?
        Ответить
        • Ну если буфер совсем-совсем константный - то норм. А если он меняется во время вызова - эскобар.чпег
          Ответить

    Добавить комментарий