Pull to refresh

Comments 49

Спасибо за статью. Где-то я читал, что решение использовать

char* s = "...\0"


вместо

struct string { 
    char* s;
    uint length; 
}

— одна из самых страшных ошибок в дизайне языка программирования.
Если уж на то пошло, то не

struct string { char* s; uint length; }

а

struct string { uint length; char[] s; }

Тогда уж size_t lenght. Во-вторых, open array — это нестандартное расширение. Чем оно удобней обычного указателя?
Во-вторых, open array — это нестандартное расширение.

Вы в этом уверены?

Стандарт C99, пункт 6.7.2.1, абзац номер 2.
цитата
A structure or union shall not contain a member with incomplete or function type (hence, a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.
Всё верно, это меня gcc смутил, когда с опцией -pedantic на эту конструкцию ругался.
Удобнее тем, что не тратятся дополнительные sizeof(char*) байт на указатель + не надо лишний раз прыгать по памяти.
Я видел, что в некоторых местах написано «char[0] s; // for compatible».
Непонятно, для кого эта статья. Эти «ловушки» известны каждому, кто хоть знаком с С/С++ на уровне, следующим за «hello world!»
Можете объяснить?
IDE под рукой нет, онлайн компилятор вывел «r».
Берётся символ с индексом три у строки «habrahabr».
Особенность C. Так же успешно можно выполнить код:
int k[5]={0, 1, 4, 9, 16};
std::cout << 3[k] << std::endl;

На выходе будет 9.
Запись вида
array[index]
компилятор представляет как
*(array + index)
Поскольку операция сложения коммутативна, нет никакой разницы между "array+index" и "index+array". Следовательно
3["habrahabr"]
это то же самое, что
"habrahabr"[3]
то есть «четвертый по счету элемент константного массива, содержащего строку 'habrahabr'»
Оператор [] разворачивается для указателя в смещение. массив[2] = *массив + 2. Сложение коммутативное, поэтому 2 + *массив то же самое. В итоге, 3[«habrahabr»] разворачивается в 3 + символьный массив.
Извините, долго писал. :)
[] для обращения к элементу массива это просто синтаксический сахар
x = str[y];
на самом деле эквивалентно
x = *(str+y);
а т.к. от перестановки слагаемых сумма не меняется, то также эквивалентной оказывается конструкция
x = y[str]; => x = *(y+str);
UFO just landed and posted this here
Стандартно народ «плавает» на integral promotions и на вообще преобразованиях базовых типов. Ещё есть грабли в тернарном операторе, передаче/приёме параметров в эллипсис, например.
Детский сад. Именно в этом заведении детей учат не забывать нолик в конце строки.
Автор явно не посещал его, от того и удивляется банальным вещам.

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

while(*str1++ = *str2++);
Статья поучительная для начинающих, но бессмысленная для сколь бы то ни было бывалых программистов. И в довесок весьма короткая.

C++ позволяет выстрелить себе в ногу тысячью и одним способом, Вы же описали лишь «неочевидности», перенесённые из чистого C.
Строки — да, а вывод символа вместо байта?
Тоже достаточно тривиальный баг. Достаточно пройти и посмотреть на определение uint8_t.
Почему возникает такое поведение — абсолютно понятно. Мысль в том, что это поведение отличается от некого «идеального».
Ну, «идеальных» языков программирования не существует. =)
И это далеко не самый хитрый способ получить не то, что ожидаешь. К примеру, можно сделать так:

class Foo
{
public:
    virtual void doWork() = 0;
    virtual void cleanup() = 0;
    virtual ~Foo()
    {
        cleanup();
    }
}

class Bar : public Foo
{
public:
    void doWork()
    {
         printf("working...\n");
    }
    void cleanup()
    {
        printf("cleaning up...\n");
    }
}

Здесь вот возникнет exception при использовании объекта класса Bar. Почему?
Здесь нет генерации exception (то есть никакими try/catch от него не защититься). Здесь undefined behaviour.
Это типичный ill-formed код. Любой современный компилятор должен ругать его еще на этапе компиляции. Ну или, учитывая то, что компилятор в данном случае наверняка проигнорирует модификатор virtual и будет использовать именно Foo::cleanup(), то ругаться будет уже линкер. Так или иначе программа не должна компилироваться.
Я думаю, это пример утрирован для наглядности. Если вызов cleanUp() будет обёрнут в какой-нибудь невиртуальный метод, то всё замечательно скомпилируется и получится pure virtual function call для VS.
Ещё пара способов инициализации массивов символов для полноты коллекции:

char str4[] = {"1234"}; // sizeof(str4) == 5
char str5[4] = {"1234"}; // sizeof(str5) == 4
Работает ли это в С++?
С str5 в C++ не работает, надо будет глянуть различия в стандартах на этот счёт. Но по видимому C++ исправляет этот «баг» в C.
отсутствие в языке специализированного целочисленного типа для 8-битных величин. Из-за этого char’у приходится брать на себя роль byte;

Есть. uint8_t и int8_t.
uint8_t — это не новый тип. Он реализован через typedef. А typedef нового типа, как известно, не создаёт. Из-за этого могут быть проблемы, например, такие, какие описаны в этой статье.
Да, это typedef, но это typedef на целочисленный 8-битный тип. И про какие проблемы вы говорите? Про то, что у ostream не перегружен оператор вывода для signed char и unsigned char?
typedef на целочисленный 8-битный тип

Нет такого родного типа. Есть тип char в различных вариациях. А так как «символы» и «целые числа» — это всё-таки разные категории данных (например, они должны по-разному печататься), то смешивание их может привести к проблемам.
Нет такого родного типа.

Это как? typedef на тип есть, а самого типа нет? Согласно документации, uint_8t это «Integer type with a minimum of 8 bits.»
А так как «символы» и «целые числа» — это всё-таки разные категории данных (например, они должны по-разному печататься)

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

Нет такого понятие, как «смешивание». Есть «неявное прведение типов». У него есть хорошие стороны и плохие. Вы привели пример, когда неявное приведение типов мешает. Есть обратные примеры.
typedef новый тип не создаёт. Это всего лишь синоним, более удобная (для программиста) запись. Для компилятора никаких uint_8t нет.
Да, для компилятора нет типов с таким именем. Однако это синоним родного 8-битного интегрального типа, если такой есть, конечно. И использование этого имени является единственным валидным способом сказать компилятору, что вы хотите 8-битную переменную. Таким образом в C++ есть «специализированный целочисленный тип для 8-битных величин». Имя этого типа не известно, но известно, что uint8_t является typedef'ом на него.
Что-то мы о совсем разном говорим.
Тогда позвольте узнать, о чем говорите вы? Я оспариваю ваше утверждение о том, что
отсутствие в языке специализированного целочисленного типа для 8-битных величин. Из-за этого char’у приходится брать на себя роль byte;

И аргументирую наличием uint8_t и int8_t, являющимися typdef'ами на «специализированные целочисленные типы для 8-битных величин».

Если вас всего-лишь не устраивает то, что код символа случайно тоже оказался 8-битным числом и поэтому часто путается с другими числами, то вам надо взглянуть на проблему более широко: не существует просто чисел, у чисел есть размерность и семантика. Оборачиваем их в классы, запрещаем неявные преобразования, пишем новомодные литералы, чтобы с ними было удобно работать. И ваши новые, наделенные семантикой, числа перестанут превращаться в символы, будут красиво печататься, при делении метров на секунды вы будете получать метры в секунду. И все это во время компиляции и без оверхеда. Даже есть готовые библиотеки, предоставляющие соответствующий функционал.
О том, что uint8_t может проявить себя как char там, где это не надо. В стороннем коде или, к примеру, при работе со Стандартной библиотекой.
Как по мне, то первый пример — это принятие желаемого за действительное. Тип char по изначальной логике не числовой, а символьный. Если это понимать, то не будет ошибок.
Так тип char и не используется. Используется тот тип, который предлагает язык для «беззнаковых 8-битных целых величин» — а именно, uint8_t. Но у него есть «особенности».

Я не знаю, как проще объяснить.

Вот, допустим, на C использовать typedef int BOOL — можно. И оно даже будет работать. Но не всегда, а только в некотором множестве случаев. А в других случаях будут вылезать уши int'а, а не настоящего булевского типа.

Вот и с uint8_t — то же самое.
В каких случаях не сработает if ( some_bool ) где int some_bool;?

C uint8_t я понимаю ваше негодование. Но, значит разрабы stdlib не пожелали делать двусмысленность для всех 8-битных типов. Делаем в голове исключение и выводим как static_cast<unsigned>(var_8bit) и тут их можно понять почему так поступили.
В этих-то сработает. А при печати — нет. Или, например, для таких some_bool допустимы операции ++ или --, которые смысла не несут. Половинчатое решение, «тут поём и тут поём, а вот тут я рыбу заворачивал, тут играть не надо».
Примитивы в плюсах не отличаются от машинно-зависимых типов, об этом сказано. Всё остальное можно реализовать самому. Просто немного не понимаю почему от него ожидают большего :)
Может внезапно не сработать if (some_bool1 == some_bool2)
Более того, char и byte в C не обязательно 8-битные. Согласно стандарту они содержат CHAR_BIT (5.2.4.2.1, 6.2.6.1) бит.
Да. Он обязан быть минимум 8 бит, но может быть и больше. Есть системы, где char аж 32-битный. Стандарту это не противоречит.
Есть статьи бесполезные, а эта просто вредная.
Sign up to leave a comment.

Articles

Change theme settings