Comments 87
0 соответствует true в Ruby
А в языке Perl существует "истинный ноль" — как число он равен 0, но как булево значение он равен истине. Кажется, он записывается как "0 but true".
Оператор sizeof обрабатывается в процессе компиляции, что даёт ему интересные свойства
Вы в примере неправильно применили sizeof. Он нужен для переносимости программ с одной машины на другую, т.к. не везде количество байт в каком-то типе переменной одинаково. Какой смысл вызывать sizeof(i++), если переменная i будет иметь всегда одно и тоже количество байт?
sizeof нужен, чтобы узнать количество байт. И его можно использовать с разными целями. В том числе и для реализации переносимости между разными архитектурами, и для стрельбы в ногу.
И его можно использовать с разными целями.
У него одна цель — сколько байт занимает тот или иной тип данных. Зачем в него пихать выражение?
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)?
1) sizeof(T) возвращает размер типа T в байтах.
2) sizeof(char) == 1 всегда, следовательно char и есть байт по размеру.
3) Платформозависимым является не размер типа char, а количество бит в одном байте (а значит и в одном char-е). За это отвечает константа CHAR_BIT.
Источник: документация по C++
В языках С и С++ sizeof(char)
всегда по определению равно 1.
Существуют платформы где байт (в широком смысле) не равен байту (в привычном смысле), размер char на всех платформах равен байту (в широком смысле) ⊨ размер char не на всех платформах равен байту (в привычном смысле)
Байт (в привычном смысле) называется «октет».
Байт (англ. byte) (русское обозначение: байт и "Б"; международное: B, byte)[1] — единица хранения и обработки цифровой информации; совокупность битов, обрабатываемая компьютером одномоментно. В современных вычислительных системах байт состоит из восьми битов и, соответственно, может принимать одно из 256 различных значений (состояний, кодов). Однако в истории компьютерной техники существовали решения с иными размерами байта (например, 6, 32 или 36 битов), поэтому иногда в компьютерных стандартах и официальных документах для однозначного обозначения группы из 8 битов используется термин «октет» (лат. octet).
char по размеру всегда равен байту, просто я использовал байт в значении «8 бит», что неверно, когда речь идёт об особенностях платформы.
Фактически, sizeof действительно возвращает размер в байтах, но для лучшего понимания кроссплатформенности, можно использовать мою терминологию (я не придумал её, а где-то стырил), где sizeof возвращает размер в количестве char, которые влезут в переменную.
Это абстрагирует от байта, который можно по ошибке принять за 8 бит, а наводит на мысль «а сколько места занимает char?».
По поводу === в JS: он выглядит как синтаксическая ошибка и вводит в ступор начинающих программистов. === означает сравнивание не только по значению, а по типам.
А «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" в рамках всего файла, а не только строки.
Видимо это применимо к 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
Даже если положить это в файл.
Начало индексов с единицы в Lua, Smalltalk, MATLAB и др…А почему это плохо? Первый элемент — первый индекс.
Со спецификации языка (то есть, документированное поведение, не баг!), которое мне на днях попортило нервы как наследие от предыдущего программиста:
<?php count(false) == 1 //true
count(null) // 0
count([]) // 0
тут все логично.<?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;
Если не указывать второй индекс то возвращается адрес.
Двумерный массив — это массив массивов. Поэтому при указании только одного индекса возвращается не "адрес", а массив — 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 воспользуюсь.
>>> 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 всегда указывает на один и тот же массив.
Но вы правы, это действительно один из самых неочевидных нюансов питона, на котором спотыкаются многие начинающие питонисты.
Параметры по умолчанию инициализируются один раз в момент инициализации функции. Поэтому с мутабельными объектами (вроде списков) в качестве дефолтных параметров и получается вот такая беда. И как уже выше написали, у многих начинающих с этим проблемы, т.к. поведение действительно очень неочевидное, если знаком с питоном поверхностно.
Собственно, решение простое — использовать immutable значения в качестве параметров по умолчанию. Как правило используют None с последующей инициализацией в теле функции:
def my_func(my_arg=None):
if my_arg is None:
my_arg = []
...
В некоторых случаях также можно использовать пустой кортеж, он во многом аналогичен пустому списку. Но тут уже зависит от самой функции.
False ** False == True
# Результат этого сравнения - True
Ну так-то, 0^0 — это неопределенность, а не единица. Так что в любом случае сложно назвать это очевидным.
Хотя если знать, что с интами питон ловко возвращает 1 для 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.
И, кстати, устоявшийся перевод есть — «пустое произведение»
Например, известная формула (x+y)n = Σk Ckn xk yn-k была бы неприменима при x=0 или y=0 если бы 00 было бы определено как-то кроме 1.
> 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 '('.
Это грубо не верно. И в С, и в С++ такое выражение порождает неопределенное поведение, а никакие не "13 или 14". Даже после введения более жестких правил sequencing в С++11, С++14 и С++17 это выражение все равно порождает неопределенное поведение в С++.
lurkmore.to/++i_+_++i
У меня основной рабочий язык — Delphi, там подобных чудес, к счастью, минимум. За что и нравится, среди прочего.
0 соответствует true в Ruby
Еще в линуксовых шеллах во всех.
sizeof(x+=1);
Кстати, компилятор люто паникует.
предупреждение: statement has no effect [-Wunused-value]
sizeof(x+=1);
Так что, не забывайте посматривать на ворнинги компилятора.
Плохо проверяли. В языке С никогда не было никакого встроенного bitand
. Эти слова в языке С всегда были макросами из <iso646.h>
. Никуда они не делись — подключайте <iso646.h>
и будет вам ваш bitand
.
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. И предупреждает при компиляции.
Не любят триграфы современные компиляторы…
Вредные заклинания в программировании