1. Си / Говнокод #25349

    0

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    #include <stdio.h>
    #include <stdlib.h>
    #include <inttypes.h>
    
    int main(void)
    {
      char a[8], b[8];
      char *a_ptr = a+8;
      char *b_ptr = b;
      printf("a_ptr = %p, b_ptr = %p\n", a_ptr, b_ptr);
      if (a_ptr != b_ptr)
      {
        printf("a_ptr != b_ptr\n");
      }
      else
      {
        printf("a_ptr == b_ptr\n");
      }
      
      
      if ((uintptr_t)a_ptr != (uintptr_t)b_ptr)
      {
        printf("(uintptr_t)a_ptr != (uintptr_t)b_ptr\n");
      }
      else
      {
        printf("(uintptr_t)a_ptr == (uintptr_t)b_ptr\n");
      }
      return EXIT_SUCCESS;
    }

    Что по-вашему тут происходит?

    Запостил: j123123, 02 Февраля 2019

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

    • Undefined behavior?
      Ответить
      • Необязательно, давайте например взглянем на драфт стандарта C17:
        https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf#subsection.6.5.9

        > Two pointers compare equal if and only if both are null pointers, both are pointers to the same object(including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space. .111)

        > 111)Two objects may be adjacent in memory because they are adjacent elements of a larger array or adjacent members of a structure with no padding between them, or because the implementation chose to place them so, even though they are unrelated. If prior invalid pointer operations (such as accesses outside array bounds) produced undefined behavior, subsequent comparisons also produce undefined behavior.
        Ответить
        • Используя 3 с компилятора на этом сайте https://rextester.com/ я получил следующее поведение:
          1)gcc и clang печатают разные адреса и, соответственно, сообщение о том что не равны и так, и эдак справедливо.
          2)vc проработав секунд 10, не печатает ничего.
          Более того, согласно приведенному тобой стандарту, "or because the implementation chose to place them so, even though they are unrelated"
          Каким компилятором ты пользовался?
          Ответить
          • https://wandbox.org/permlink/cNuUphyNPKdUibRL
            Ответить
          • Ах да, на мой багрепорт по этому поводу сказали, что это Dup: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89157
            Ответить
            • Я прочитал ветку, которую считают оригиналом твоего репорта, как я понял, gcc оптимизирует
              if (a_ptr != b_ptr)
                {
                  printf("a_ptr != b_ptr\n");
                }
                else
                {
                  printf("a_ptr == b_ptr\n");
                }
              в
              printf("a_ptr != b_ptr\n");

              Потому что он видит что у указателей разное "происхождение", и ему становится похуй.
              https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61502#c30
              Ответить
              • Хуёвая какая-то оптемерзация, кунилятор же знает как всё размещено, вот если бы он насрал на происхождение и всё честно посчитал и сократил бы до
                printf("a_ptr == b_ptr\n");
                вот эт было бы другое дело.

                Вот в S" Forth" все оптимизации делает программист, именно поэтому я за S" Forth".
                Ответить
                • Обращаюсь к местным знатокам с++: возможно ли применить арифметику указателей в compile-time чтобы получить результат, описанный пользователем с никнеймом Rooster с помощью if constexpr?
                  Ответить
                  • Вообще не знаю C++. Полез копать. Constexpr-функция должна состоять из выражения return, причём вызывать функции с побочным эффектом или создавать объекты мы не можем.

                    Вот так, например, можно:
                    constexpr const char * compare(int *a_ptr, int *b_ptr) {
                       return (a_ptr != b_ptr) ? "a_ptr != b_ptr" : "a_ptr == b_ptr";
                    }
                    Ответить
                  • Нет. Адреса локалок не constexpr.
                    Ответить
                    • Замечу, что в S" Forth" не нужке никакой constexpr, [ ( счиьаем что нада ) ] literal и всё. Именно поэтому я за [ .( Введите название языка: ) pad 5 accept pad swap ] sliteral
                      Введите название языка: Forth >ok
                      Ответить
                      • Х.з., я вот не осилил переход к чему-то более-менее сложному на форте. Вместо няшных йода-предложений у меня получалась какая-то нечитаемая хуйня из dup drop и swap.
                        Ответить
                        • > нечитаемая хуйня из dup drop и swap
                          о да, это один из главных минусов. Потому и пишут стековые диаграммы, я даже в сложных случаях на каждой строчке их пишу. Можно облегчить страдания если стараться писать маленькие определения, ещё облегчают дело LOCALS|
                          : space?  ( c -- flag )  bl = ;
                          
                          : -leading ( c-addr1 u1 -- c-addr2 u2 )
                             locals| n s | \ в обратном порядке
                             begin
                               s [email protected] space? n 0<> and
                             while
                               s 1+ to s
                               n 1- to n
                             repeat
                             s n
                          ;
                          Ответить
                          • Ну ладно low-level, там можно ещё со стеком попердолиться. Но высокоуровневый код тоже не получается простым и наглядным :(
                            Ответить
                    • Действительно...

                      А адреса глобальных переменных могут быть constexpr? Допустим, что программа может загружаться по произвольному адресу, но при этом смещения глобальных объектов относительно начала образа фиксированы.
                      Ответить
                • Думаю что компилятор принимает решение о том, как именно размещать всякую хуйню на стеке (в каком порядке) сильно позже, чем происходит стадия всяких таких оптимизациий.
                  Ответить
                  • Похоже на то. Оптимизатор считает, что y следует не сразу за x, а лежит где-то в произвольном месте, потому и &x + 1 != &y. А потом уже с ключом -O2 у меня получилось, что они лежат друг за другом и плотно.

                    Кстати, нашёл не очень старое говно:
                    http://govnokod.ru/24539

                    Забавно, что 25349 получается из 24359 перестановкой цифр.
                    Ответить
                  • Ну кунилятор же знает как он всё размещать будет, и порядок должен быть как в исходнике (нахуя их вообще перемешивать?), можно спокойно считать смещения.
                    Ответить
                    • 1. Выравнивание. Некоторые процессоры не умеют читать данные, размещённые по нечётным (ну или каким-нибудь некруглым) адресам, потому что шина памяти как-то хитро подшита. Приходится вставлять две инструкции чтения и потом сдвигать данные. x86 умеет читать с нечётного адреса, но делает это медленно (ибо два чтения и сдвиги производятся где-то внутри процессора).

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

                      2. Я чуть выше написал про предварительное выделение памяти в стеке и про push. Предварительное выделение и push приводят к размещению локальных переменных в противоположных порядках.

                      3. Часть локальных переменных компилятор может утоптать в регистры, чтобы не тратить память.

                      *****

                      Да, компилятор знает, как он это будет делать. Но у «gcc», вероятно, проблема из-за его кроссплатформенности. Нужно поддерживать кучу рахит-тинктур. Парсер, оптимизатор, кодогенератор разделены. Чтобы они могли сообщать друг другу дополнительную информацию, нужны хаки.
                      Ответить
                    • Ещё одно замечание про локальные переменные.

                      Зачем смешивать способы выделения?

                      В «Паскале» выделять переменные можно только в самом начале функции. Там всё просто: сразу выделил место в стеке и разместил подряд в прямом порядке.

                      В «C99» и в «C++» переменные можно выделять в операторе «for» и в некоторых других. Они могут занимать место в стеке временно, ведь они вне блока не используются (использовать их вне блока –— UB), поэтому их можно пушить. И вот это приводит к перемешиванию адресов.

                      Итого: взятие адреса локальной переменной –— это довольно небезопасная операция, выносящая мозг оптимизатору.
                      Ответить
                      • https://img4.goodfon.ru/original/2500x1730/4/fc/lis-lisa-zima-zlaia-zloi-prishchur.jpg
                        Ответить
                      • Именно поэтому я против локалок. Вот в S" Forth" кроме LOCALS| которые никто не использует нет никаких вложенных определений. Именно поэтому я за S" Forth".
                        Ответить
        • Ну написано же - только если они элементы структуры или массива. На вариант "конпелятору захотелось положить их рядом" явно нельзя полагаться.
          Ответить
          • В каком-то говнокоде даже обнаружили, что порядок размещения локальных переменных функции может быть разным: одни компиляторы сразу уменьшают указатель на вершину стека (alloca), потом размещают переменные, другие же пушат в стек каждую переменную, поэтому порядок получается противоположный. А ещё они могут лежать в регистрах и вообще не иметь адреса в памяти. Хитрые кокококомпиляторы кладут в стек только те переменные, у которых мы явно брали адрес.

            С глобальными переменными проще: они как правило размещаются в прямом порядке и в регистрах не хранятся. Но даже тут можно нарваться на паддинги: кококомпилятор может выравнивать переменные по-разному, в зависимости от параметров оптимизации.
            Ответить
    • https://kristerw.blogspot.com/2016/03/c-pointers-are-not-hardware-pointers.html

      GCC has a somewhat aggressive interpretation on the standard, so it compiles p == q to false if it determines that they are derived from different objects (see GCC bug 61502 for details). This has the fun effect that it is possible to get pointers p and q that point at the same memory address, but p == q evaluates to false, as in
      #include <stdio.h>
      
      int main(void)
      {
          int x, y;
          int *p = &x + 1;
          int *q = &y;
          printf("%p %p %d\n", (void*)p, (void*)q, p == q);
          return 0;
      }


      that prints

      0x7f7fffffdafc 0x7f7fffffdafc 0

      on my development machine when compiled with a recent GCC.
      Ответить
      • Кокококошмар!
        Ответить
      • Проверил, «MSVC», «Intel C», «Watcom C», «Borland C», «Digital Mars» честно сравнивают &x+1 и &y.

        При разных значениях параметра оптимизации они могут разместить x и y на разном расстоянии друг от друга, но всегда возвращают правильный результат (p == q, если нет дыр, и p != q, если есть дыры). «GCC» же всегда возвращает p != q вне зависимости от того, как реально размещены x и y.
        Ответить
      • #include <stdio.h>
        
        int main(void)
        {
            int x, y;
            int *p = &x + 1;
            int *q = &y;
            printf("%p %p %d\n", (void*)p, (void*)q, p == q);
            printf("p < q? %d\n", p < q);
            printf("p > q? %d\n", p > q);
            return 0;
        }


        gcc без оптимизации:
        0022FED8 0022FED0 0
        p < q? 0
        p > q? 1
        Что ж, всё верно: p≠q, p>q.


        gcc -O2:
        0022FEDC 0022FEDC 0
        p < q? 0
        p > q? 0
        Получилось, что p не больше q, p не меньше q, но p и не равно q. Как будто сравнили два NaN.
        Ответить
        • Больше ада:
          #include <stdio.h>
          
          int main(void)
          {
              int x, y;
              int *p = &x + 1;
              int *q = &y;
              printf("%p %p %d\n", (void*)p, (void*)q, p == q);
              printf("p < q? %d\n", p < q);
              printf("p > q? %d\n", p > q);
              printf("p <= q? %d\n", p <= q);
              printf("p >= q? %d\n", p >= q);
              printf("p != q? %d\n", p != q);
              printf("p - q? %d\n", p - q);
              return 0;
          }


          Итог (gcc -O2):
          0022FEDC 0022FEDC 0
          p < q? 0
          p > q? 0
          p <= q? 1
          p >= q? 1
          p != q? 1
          p - q? 0

          Итак, p <= q; p >= q; p - q == 0, но при этом p != q.
          Ответить
          • Короче, посоны, вместо p==q теперь пишем (p<=q)&&(p>=q) или p-q==0.
            Ответить
            • Не математично. Надо !(p<q)&&!(p>q).
              Ответить
              • Или !((p<q)||(p>q)), чтобы меньше операций было.
                Ответить
                • Придумал:
                  if ((p != q) && !((p < q) || (p > q))) {
                      printf("Укокококозатели равны, но имеют разное происхождение.\n");
                  }

                  Правда, пока не знаю, в каком коде это можно применить.
                  Ответить
                  • Не сработало. А вот так работает:
                    if ((p != q) && !(p - q)) {
                          printf("Укокококозатели равны, но имеют разное происхождение.\n");
                    }
                    Ответить
    • Пофиксил, проверь:
      - if (a_ptr != b_ptr)
      + if (a_ptr - b_ptr)
      Ответить
    • Я люблю, когда парни пишут грамотно. Вместе с тем я разочарован: я узнал, что ты - одна из фаек инкануса. Неужели, кроме тебя и борманда здесь больше никого нет?
      Ответить
    • Ебануться
      Ответить

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