Как стать автором
Обновить

Комментарии 87

0 соответствует true в Ruby

А в языке Perl существует "истинный ноль" — как число он равен 0, но как булево значение он равен истине. Кажется, он записывается как "0 but true".

А чему во всех этих языках равен False?

В ruby false это nil(аналог null) и false. Всё остальное true.
НЛО прилетело и опубликовало эту надпись здесь
Оператор sizeof обрабатывается в процессе компиляции, что даёт ему интересные свойства

Вы в примере неправильно применили sizeof. Он нужен для переносимости программ с одной машины на другую, т.к. не везде количество байт в каком-то типе переменной одинаково. Какой смысл вызывать sizeof(i++), если переменная i будет иметь всегда одно и тоже количество байт?

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

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

У него одна цель — сколько байт занимает тот или иной тип данных. Зачем в него пихать выражение?
Он нужен для переносимости программ с одной машины на другую

Его можно использовать в том числе и для этого.


Зачем в него пихать выражение?

sizeof(str)-1
sizeof(str-1)
Бывают и просто очипятки.

sizeof нужен, чтобы узнать количество байт.

Таки чтобы узнать сколько влезет char, которые не везде по размеру равны байту.
The sizeof operator gives the amount of storage, in bytes
© MSDN

sizeof generates the size of a variable or datatype, measured in the number of char size storage units required for the type
© Wikipedia

You can use the sizeof operator to obtain the size (in bytes) of the data type of its operand
. © The GNU C Reference Manual

Разные источники утверждают разное. Есть на примете архитектура, где sizeof(char) != sizeof(byte)?
Что такое sizeof(byte)?

1) sizeof(T) возвращает размер типа T в байтах.
2) sizeof(char) == 1 всегда, следовательно char и есть байт по размеру.
3) Платформозависимым является не размер типа char, а количество бит в одном байте (а значит и в одном char-е). За это отвечает константа CHAR_BIT.

Источник: документация по C++
Есть на примете архитектура, где sizeof(char) != sizeof(byte)?

Здесь есть примеры подобного.

В языках С и С++ sizeof(char) всегда по определению равно 1.

Что никак не противоречит тому, что я написал, а скорее подтверждает.
НЛО прилетело и опубликовало эту надпись здесь
Байт в широком смысле — это минимальный адресуемый объем памяти, и в стандарте он употребляется именно в этом смысле. В привычном же смысле байт — это 8 бит.

Существуют платформы где байт (в широком смысле) не равен байту (в привычном смысле), размер char на всех платформах равен байту (в широком смысле) ⊨ размер char не на всех платформах равен байту (в привычном смысле)

Байт (в привычном смысле) называется «октет».


Байт (англ. byte) (русское обозначение: байт и "Б"; международное: B, byte)[1] — единица хранения и обработки цифровой информации; совокупность битов, обрабатываемая компьютером одномоментно. В современных вычислительных системах байт состоит из восьми битов и, соответственно, может принимать одно из 256 различных значений (состояний, кодов). Однако в истории компьютерной техники существовали решения с иными размерами байта (например, 6, 32 или 36 битов), поэтому иногда в компьютерных стандартах и официальных документах для однозначного обозначения группы из 8 битов используется термин «октет» (лат. octet).
Официально — да. Но в школах на уроках информатики всех учат что байт — это ровно 8 бит, не больше и не меньше. Объем жестких дисков тоже измеряется в строго восьмибитных байтах, хотя к архитектуре процессора они, строго говоря, не привязаны.
Да, я был неправ в терминологии.
char по размеру всегда равен байту, просто я использовал байт в значении «8 бит», что неверно, когда речь идёт об особенностях платформы.

Фактически, sizeof действительно возвращает размер в байтах, но для лучшего понимания кроссплатформенности, можно использовать мою терминологию (я не придумал её, а где-то стырил), где sizeof возвращает размер в количестве char, которые влезут в переменную.
Это абстрагирует от байта, который можно по ошибке принять за 8 бит, а наводит на мысль «а сколько места занимает char?».

По поводу === в JS: он выглядит как синтаксическая ошибка и вводит в ступор начинающих программистов. === означает сравнивание не только по значению, а по типам.

Наоборот. === проще — это просто сравнение. Т.е. сравнение по ссылки, а в случае примитивов — по значению. А вот == — приведение к общему типу и последующее сравнение. Приведение осуществляется методами toString или valueOf.
В питоне «is» и не предназначен для сравнения значения переменных, для этой цели служит вполне стандартное "=="
А «is» проверяет, указывают ли две переменный на один и тот же объект в памяти. Это достаточно специальная операция, и её нужно применять только в тех редких случаях, когда вы чётко понимаете, зачем вам нужна именно такая проверка, а не простая проверка на равенство значений.

Единственный вариант, когда «is» можно использовать просто так — это для сравнения с None.

Касательно Python.


>>> a = [-10, -10]
>>> a[0] is a[1]
True
>>> b = [-10]
>>> b.append(-10)
>>> b[0] is b[1]
False
>>> b[0] is a[0]
False

Причина — в оптимизациях. В интерактивном режиме единицей трансляции является строка. Соответственно, интерпретатор выполняет (и оптимизирует) каждую строку независимо от других. Если в этой одной строчке несколько раз используется одна и та же константа, то оптимизатор способен это заметить и создать единственный объект этой константы под нужды всей строки. Если же одна и та же константа используется в нескольких строках, то интерпретатор уже не в силах оптимизировать такой вариант.


К слову, если код выше поместить в файл и выполнить его, то вывод станет


True
True
True

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

В любом случае, сложно придумать кейс, при котором будет иметь значение, какой результат возвращает оператор is, примененный к двум примитивам.

Конечно, с этим сложно поспорить. Просто немного прояснил природу явления для тех, кому интересно.

Видимо это применимо к Python2. Там если записать в одну строку, то результат поменяется


>>> b = [-10]; b.append(-10); b[0] is b[1]
True

Но в Python 3 эта же строка вернет False. Более того, даже для создания списка сразу из двух элементов будет False


>>> b = [-10, -10]
>>> b[0] is b[1]
False

Даже если положить это в файл.

На Python 3 «-10» воспринимается не как отдельная константа, а как операция унарного минуса, применённая к константе 10. Замените «-10» на 500 и поведение в третьем питоне станет аналогичным поведению во втором.

Начало индексов с единицы в Lua, Smalltalk, MATLAB и др…
А почему это плохо? Первый элемент — первый индекс.
В языках которые умеют манипулировать указателями — очень удобно вычислять адрес в памяти домножая индекс на размер элемента + смещение. В языках где нет указателей — это не важно, но у истинных разработчиков которые начинали с низкоуровневых языков резонно бомбит от такой нумерации.
Ну да, в Lua, Smalltalk, MATLAB ведь не умеют манипулировать указателями :)
Ни одной шутки про PHP.
Со спецификации языка (то есть, документированное поведение, не баг!), которое мне на днях попортило нервы как наследие от предыдущего программиста:
<?php count(false) == 1 //true
Ну так а зачем пытаться считать количество элементов в false?
count(null) // 0
count([]) // 0
тут все логично.
Функция может возвращать [], если количество результатов равно нолю или false, если возникла ошибка при запросе.
Да, логичнее бросать Exception, но о них не все PHP-программисты знают.
насколько могу судить, там срабатывает привидение типов к массиву
<?php
var_dump((array)false); // array(1) { [0]=> bool(false) }
var_dump((array)0);     // array(1) { [0]=> int(0) }
var_dump((array)null);  // array(0) { }

var_dump(count(false)); // int(1)
var_dump(count(0));     // int(1)
var_dump(count(null));  // int(0)

всё это работает отлично до php 7.2, начиная с 7.2 добавился warning:
Warning: count(): Parameter must be an array or an object that implements Countable in ...

Недавно узнал что можно делать так:


BOOL CTigerTree::IsZeroBlock(uint32 nBlock) const
{
    static const uint64 ZeroHash[37][3] =
    {
...
    };
...
CTigerNode* pBase = m_pNode + m_nNodeCount - m_nNodeBase + nBlock;
return memcmp( ZeroHash[ m_nActualHeight - m_nHeight ], pBase->value, sizeof( pBase->value ) ) == 0;

Если не указывать второй индекс то возвращается адрес.

Передача в memcmp указателя и размера этого указателя выглядит как баг…

Там тоже массив.


class HASHLIB_API CTigerNode
{
public:
    CTigerNode();
    uint64  value[3];
    bool bValid;
};

В дебагере результат 24 как и положено.

Двумерный массив — это массив массивов. Поэтому при указании только одного индекса возвращается не "адрес", а массив — lvalue типа uint64 [3]. А уж "адресом" он становится позже, благодаря тому, что в вашем контексте (аргумент memcmp) происходит автоматическое преобразование типа "массив" к типу "указатель".

Тут же в посте:


int x[1] = { 0xdeadbeef };

printf("%x\n", 0[x]); // prints deadbeef

Причина работы такого кода в том, что array[index] на самом деле просто синтаксический сахар для *(array + index). Благодаря коммутативному свойству сложения можно поменять их местами и получить тот же результат.

Я недавно сделал так:


    inline static DWORD Compress(IN6_ADDR nAddress)
    {
        DWORD* n = (DWORD*) &nAddress;
        return n[0] ^ n[1] ^ n[2] ^ n[3];
    }

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


Что касается второй части — нет, так делать нельзя. Это грубое нарушение правил strict aliasing. Работать может только чисто на везении.

Какие могут быть проблемы во втором коде?

Компилятор может подумать что результат не зависит от входного значения и оптимизировать функцию в return 0 или вообще посчитать что она никогда не вызывается и выкинуть вызывающий ее код. UB такое UB...

Как правильно заметил mayorovp, компилятор вправе игнорировать информационную зависимость между n[] и nAddress, т.е. полагать, что эти значения никак между собой не связаны и никак друг на друга не влияют. Манипуляции со значением n[] могут быть свободно перенесены вверх по коду — туда, где nAddress еще не получил правильного значения.

Прчитал статью "Про C++ алиасинг, ловкие оптимизации и подлые баги".
Меня этот баг не коснулся потому что:


Что насчет MSVC?

Проблемы про strict aliasing там нет. Более того, возможности включить тоже нет. Видимо, MS приняло решение, что C99-compliant кода в мире без тонких граблей про aliasing в мире куда меньше, чем какого обычно, поэтому незачем создавать сложности. Просветительскую миссию осуществляет gcc, ну и бажок-другой иногда втихую нагенерит, не без этого.

Но в то же время там есть подсказка сделать тожесамое используя union. Может попробую этот вариант. Мне вот только не нравится двойное копирование.


А может и Word'ами из самого IN6_ADDR воспользуюсь.

Правила strict aliasing пристутвует в стандарте С++ с начала времен. А "проблемы про strict aliasing там нет" лишь означает, что оптимизаций, основанных на использовании правил strict aliasing в MSVC пока не реализовали. Сегодня не реализовали — завтра реализуют.

НЛО прилетело и опубликовало эту надпись здесь
>>> x = -5
>>> y = -5
>>> x is y
True

>>> x = -6
>>> y = -6
>>> x is y
False

А что странного? В документации указано, что is проверяет, одинаковые ли это объекты(не значения), никто не гарантирует, какие будут одинаковыми, а какие нет. Лучше было бы показать неочевидное поведение параметров по умолчанию в функции, например:

>>> def append_to_array(value, array=[]):
...     array.append(value)
...     return array
>>> print(append_to_array(10))
[10]
>>> print(append_to_array(11))
[10, 11]
>>> print(append_to_array(12))

Это происходит потому что параметры по умолчанию создаются во время объявления функции(например при импорте модуля) и array всегда указывает на один и тот же массив.
Вот это на самом деле странно. Согласно заголовку функции, значение array по умолчанию — пустой массив, а во втором случае функция вызывается с параметрами (11, [10]) вместо (11, []) как должно быть
Согласно заголовку функции, значение по умолчанию — вовсе не пустой массив, а вот этот вот конкретный массив, который только на данный момент пуст, но может измениться в будущем.

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

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


Собственно, решение простое — использовать immutable значения в качестве параметров по умолчанию. Как правило используют None с последующей инициализацией в теле функции:


def my_func(my_arg=None):
    if my_arg is None:
        my_arg = []
    ...

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

Кто-то еще пишет сишный код с триграфами? Кстати, даже с ними нельзя конструировать строку дефайна в макросе.
Кстати, раз уж речь зашла о логических переменных в питоне, то можно было бы вспомнить и эту хохму:
False ** False == True
# Результат этого сравнения - True

А почему должно быть иначе?
0^0 = 1
Для тех, кто уже знает, что bool наследуется от int, всё действительно очевидно. А вот остальным это вполне может взорвать мозг.

Ну так-то, 0^0 — это неопределенность, а не единица. Так что в любом случае сложно назвать это очевидным.
Хотя если знать, что с интами питон ловко возвращает 1 для 0**0, вместо выбрасывания ошибки, то действительно все встает на свои места)

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

В математике единице равен предел x^x при x стремящемся к нулю справа. А именно 0^0 — это неопределенность, как не крути. И то, что ее принимают за единицу — это именно особенность языка. И факториал нуля тут совершенно не к месту. Он равен единице как «empty product» (хз, есть ли устоявшийся перевод на русский) https://en.m.wikipedia.org/wiki/Empty_product.

Тогда уж посмотрите и эту статью:
en.wikipedia.org/wiki/Zero_to_the_power_of_zero
Вы не поверите, но там тоже упоминается empty product.
И, кстати, устоявшийся перевод есть — «пустое произведение»
Принимать 00 за единицу рекомендовал еще как минимум Кнут. Потому что с таким определением многие формулы упрощаются лишаясь особых случаев.

Например, известная формула (x+y)n = Σk Ckn xk yn-k была бы неприменима при x=0 или y=0 если бы 00 было бы определено как-то кроме 1.
Erlang:
> if 
    0 -> true; 
    true -> false 
  end.    

false

true и false в Erlang — это отдельные атомы, ни с нулём, ни с единицей, ни с каким-либо другим числом не связанные.

А мне в Python нравился трюк с изменением tuple


>>> t = ([1, 2], [3, 4])
>>> t[0].append(10)
>>> t  # сработало, кортеж содержит тот же список, но в нем новый елемент
([1, 2, 10], [3, 4])
>>> t[0] = t[0] + [20] #  TypeError: 'tuple' object does not support item assignment
>>> t[0] += [30]  # Сокращенная запись, тот же TypeError, но...
>>> t
([1, 2, 10, 30], [3, 4])

И пока я искал этот кусочек кода наткнулся на wtfpython @ Github с подборкой таких моментов. Кажется на Хабре даже был перевод той статьи. Вот с chained operations по ссылке мне понравился

А в чём трюк-то?
В строчке, где происходит ошибка, вы говорите питону «Запиши в нулевой элемент тупла результат выражения». Естественно питон отказывается, потому что в тупл нельзя ничего записывать.
В следующей строчке вы говорите питону «Возьми нулевой элемент тупла, и проделай с ним вот такую манипуляцию». И здесь всё нормально.

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


Потому как list.__iadd__ не создает новый список, в отличии от list.__add__. Так что + в += отрабатывает, а вот = выдает ошибку.


Может слово трюк не подходит, но "wtf tuple изменился" реакцию у некоторых вызывает

Хватает в языках весёлухи:

Неопределённое поведение

В качестве еще одного примера неопределенного поведения можно привести код:

int i = 5;
i = ++i + ++i;
При его выполнении переменная i может принять значения 13 или 14 для C/C++, 13 для Java, PHP и C#, 12 при реализации на LISP.
Оффтоп, но по этому поводу вспоминается операция передёргивания
--i++
шутка выглядит интересно, но это не работает:
— в php, js, вылетает «syntax error»;
— в java ошибка типа — ожидаем для второй операции переменную, но получаем значение — результат первой;
— в «с» error: expected identifier or '('.

В С вы что-то не то проверяли. Выражение ошибочно, но потому, что -- требует lvalue, а ++ возвращает rvalue. Диагностическое сообщение будет другим. (Кстати, по сути совпадающее с джавовским.)

Это грубо не верно. И в С, и в С++ такое выражение порождает неопределенное поведение, а никакие не "13 или 14". Даже после введения более жестких правил sequencing в С++11, С++14 и С++17 это выражение все равно порождает неопределенное поведение в С++.

Ну и лурк можно вспомнить :)

lurkmore.to/++i_+_++i

У меня основной рабочий язык — Delphi, там подобных чудес, к счастью, минимум. За что и нравится, среди прочего.
Питон мне вообще мозг вынес, учитывая то, что еще в комментах понаписали.
0 соответствует true в Ruby

Еще в линуксовых шеллах во всех.
Ну, в шеллах-то все логично. Там 0 соответствует выполнению без ошибки (true), а любое другое число — выполнению с ошибкой (false).

Это так не только в линуксовых шеллах, кстати. Виндовый cmd.exe тоже так считает…
А в Ruby и прочих зачем так сделано? Там же наверное тоже какая-то логика была.

Ну в Lua например всё просто. Есть false и nil остальное всё воспринимается как true. При этом false не равен nil. Nil это отсутсвие значения.

Насколько помню, в руби числа также обьекты. Возможно в этом и кроется причина такого поведения.
sizeof(x+=1);

Кстати, компилятор люто паникует.
предупреждение: statement has no effect [-Wunused-value]
sizeof(x+=1);

Так что, не забывайте посматривать на ворнинги компилятора.
В том С, который есть у меня под рукой (gcc 5.4.0) нет диграфов и «слов» типа bitand — только что проверил. Но оно есть в С++. В С только триграфы.

Плохо проверяли. В языке С никогда не было никакого встроенного bitand. Эти слова в языке С всегда были макросами из <iso646.h>. Никуда они не делись — подключайте <iso646.h> и будет вам ваш bitand.

НЛО прилетело и опубликовало эту надпись здесь
Увы, вечная беда переводчиков.
public final class Integer

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }


private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
....


НЛО прилетело и опубликовало эту надпись здесь
Угу, я тоже долго пытался понять ту фразу пока не увидел оригинал и пошел проверять срц…

Оператор sizeof в языке C не обязательно обрабатывается в процессе компиляции. Если аргументом sizeof является VLA (Variable Length Array), то оператор sizeof будет вычисляться во время выполнения. Вот такое применение sizeof будет инкрементировать x


  int x = 0;
  sizeof(int[++x]);
  printf("%d\n", x);
  int x=42;
// wtf ???/
  x++;
  printf("%d\n",x);

gcc 6.4 печатает 42 — если -std=c11 или c99
А вот если std=gnu99 или gnu11 — результат 43. И предупреждает при компиляции.
Не любят триграфы современные компиляторы…

Забавный способ выстрелить в ногу преемникам ;)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории