среда, 18 апреля 2012 г.

Прелести С-строк


Странно, зачем вообще разрешать этому фрагменту компилироваться? Ну да ладно.

char* symbolArrayNoSize = new char[];
cout << symbolArrayNoSize << endl;


Имеем массив символов неопределённого размера. В результате отображения на экране скорее всего будет выводиться весь хлам до первого символа '\0'. Запуск фрагмента подтвердил предположение:


¤¤¤¤ллллллллю■ю■


Предлагаю вашему вниманию следующий фрагмент:

const int ARRAY_LENGTH = 5;
char* symbolArray = new char[ARRAY_LENGTH];
strncpy(symbolArray, "abcdefghijklmnop", ARRAY_LENGTH);
cout << symbolArray << endl;


Казалось бы, здесь всё по фэн-шую: размер массива задали, в него скопировали именно необходимое количество символов и вывели на экран. Не тут-то было:

abcde¤¤¤¤иR╕ЧиRШ¤¤¤¤

Из этого результата один прискорбный вывод: задаёшь размер массива явным образом - всё равно содержимое будет выводиться до первого встречного '\0'. Так спрашивается, какого хрена задавать размер массива, если он всё равно сомнительную роль играет? Где логика-то?


const int ARRAY_LENGTH = 5;
char* symbolArray = new char[ARRAY_LENGTH + 1];
// Length of useful data is ARRAY_LENGTH plus 1 symbol '\0',

// meaning the end of the string.
symbolArray[ARRAY_LENGTH] = '\0';
// Put the delimiter of the string on its proper position
strncpy(symbolArray, "abcdefghijklmnop", ARRAY_LENGTH);
cout << symbolArray << endl;


И только в результате вот таких манипуляций получаем ожидаемый результат:

abcde

Удобно? Не очень. Используйте обёрточные классы для С-строк. Не выделывайтесь.
Компилировал в Visual Studio 2010.

32 комментария:

  1. Полуночный бред шизофреника))))

    1. Все действия программ абсолютно логичны и правильны.

    2. Читаем Википедию, там разжевано до нельзя http://ru.wikipedia.org/wiki/Strncpy

    p.s. Как уже говорили, С - мощное оружие, которым легко убиться, если не понимать основных принцип работы. Для остального есть C# :*)

    p.s.2. Прежде чем использовать функцию _НЕОБХОДИМО_ всё же прочитать что она делает и _КАК_ она это делает http://www.cplusplus.com/

    ОтветитьУдалить
    Ответы
    1. О, главный критикан пришёл! Думал, когда ж он выползет и начнёт типа "подкалывать"? И он не заставил себя ждать! Но я ж тоже не лыком шит, правда?! Посему наношу ответный удар: ТЫ-ДЫЩ!!! Начинаю огрызаться:

      Где шизофреники? Галактика опять на моих хрупких плечах? Ахтунг!
      13:50 - это ещё не полночь, Дима, ты слегка перепутал.

      1. Шо, правда? Ты упал в моих глазах!
      Ладно, придётся объяснять неумеющему выделять главное.
      Фокус поста в том, что

      а) char* symbolArrayNoSize = new char[];
      Это компилируется, что само по себе опасно и чревато. Причин использовать такой фрагмент нет. Но ты ж говоришь, что "Все действия программ абсолютно логичны", потому рассказывай, где такое чудо мысли оправдано и логично. Не расскажешь - проиграл по этому пунктику.

      б) char* symbolArray = new char[ARRAY_LENGTH];

      При попытке отображения symbolArray на экране появляется непредсказуемое кол-во байт содржимого. Смысл тогда в явной длине ARRAY_LENGTH? Дали хотя бы точную длину.

      2. Суровым программистам, читающим Википедию, рекомендую бегло ознакомиться с документальной киношкой "Truth in Numbers?", http://www.youtube.com/watch?v=QdHfIRaxbsI Там вопрос про достоверность информации в Вики очень даже неплохо освещён.

      3. Так это ж я ещё про C# не написал! Но терпение, ждите на ваших экранах!
      4. Слов _НЕОБХОДИМО_ и _КАК_ в русском языке нет, потому не понимаю, о чём ты.
      О, кажется дошло! А сейчас будет подкол:

      featZima:
      On 12.09.2011 11:55, Дмитрий Глинский wrote:
      Итак) Первое, что ждут от любого нормального языка, соотвествие своим ожиданим, что за хрень, почему при сравнении двух одинаковых строк получаем false ???
      p.s. сейчас прийдётся открывать гугл и тратить безценное время на такую мелочь...

      Shalfey:
      Абсолютно правильное замечание.
      String s = "...";
      String t = "...";

      // comparison of two strings

      if(s.equals(t)){
      System.out.println("Strings are the same!");
      }

      // Why we can't use operation ==

      Operation == defines, if the strings are located in the same area in memory. We can't use it for comparison because of special behaviour of different virtual machines.If the virtual machine always provides collaborative use and location of the same strings, we can use the operation == to compare them. But only constant strings can be used and stored in such way. We can't use this operation with strings, that were get as results of operations + or substring for example.
      That's why it's always better to use the equals method.
      You can also use the analogue of the strcmp function from the C programming language. In Java it's named compareTo.
      if(greeting.compareTo("Help") == 0){
      System.out.println("Strings are the same!");
      }

      Shalfey to featZima:
      Джава - 2 класса в 1 файле - ты говорил, что это невозможно. Вот тебе пример, умник:

      import javax.swing.*;
      // main class
      public class sample {
      public static void main(String[] args){
      Frame frm = new Frame();
      frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frm.setVisible(true);
      }
      }
      // additional class
      class Frame extends JFrame{
      private static final int DEFAULT_HEIGHT = 100;
      private static final int DEFAULT_WIDTH = 200;
      public Frame(){
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      }
      }

      // As you can see, we've got two different classes in one file.
      // Any questions?

      Резюме: чья бы корова мычала. Конечно, проще педалить почти ничего не зная и говорить, что язык - говно, нежели почитать документацию.
      Стильно, модно, молодёжно!

      Удалить
    2. Ну ты загнул) Одно дело системная функция, класс, или что-нибудь подобное :: тут всегда нужна документация, но! когда управляемый язык переопределяет оператор сравнения строк в оператор сравнения указателей -- извольте, мне этого не понять)

      Удалить
    3. Дима, хватит строить из себя умного программиста! Кого ты обманываешь?
      Умный программист посмотрит в правильные книги/документы и прочтёт, что для любого ссылочного типа операция "==" определена в C# именно как сравнение адресов объектов. ЕДИНСТВЕННОЕ ИСКЛЮЧЕНИЕ сделано для строк - там операция "==" сравнивает значения объектов, а не их адреса. Т.е. сравнение строк с помощью вышеприведенной операции является ИСКЛЮЧЕНИЕМ ИЗ ПРАВИЛА.
      В это же время в Java такого исключения нет. Значится, более предсказуемый и продуманный подход.
      Если что - обращайся к господину Троелсену за подробностями.

      Удалить
    4. Опять же: строки -- это элементарный тип в понимании любого начинающего программиста) И хоть во многих языках есть классы для их отображения, сравниваться они должны как элементарные типы!

      Удалить
    5. Про строки - элементарный тип - С-шникам расскажи, они посмеются. Про ассемблерщиков не вспоминаю.
      Строки никому ничего не должны. Тем более тебе.

      Удалить
  2. Ну-ну, Дмитрий. Я бы не был так категоричен на Вашем месте. Не всем с полпинка понятен чистый C, а уж логику в нем искать способны лишь знатоки.
    Относительно примера: в первом блоке инициализация указателя (!) с помощью векторной формы оператора new синтаксически допустима, НО! Ты получаешь указатель на память в куче, да, но Страуструп тебя упаси что-либо туда записать — heap corruption гарантировано. Думаю, это связано с администратором кучи каким-то особым образом; во втором блоке сигнатура strncpy действительно возлагает на программиста большую ответственность за конец строки; и в третьем блоке все выглядит корректно. Обычно для простоты делают так: выделяют буфер под строку нужной длинны + 1 для нуль-терминатора, потом забивают его нулями (memset), а уж потом копируют туда данные (последний символ таким образом не нуждается в установке в ноль).
    Пока WinAPI или интерфейс других ОС используют С-строки, у нас — системных программистов нет иного выбора, кроме как разбираться в этих вопросах. Я лично по максимуму заворачиваю все в std::string, но там, где такой возможности нет — использую массивы символов и безопасные функции по работе с ними.

    ОтветитьУдалить
    Ответы
    1. Всё по делу, читать приятно!

      Удалить
    2. >>> Ты получаешь указатель на память в куче, да, но Страуструп тебя упаси что-либо туда записать — heap corruption гарантировано.

      Я же говорю, всё от незнания происходит =) Читаем выше и понимает, что класть можно и вполне, вполне безопасно)

      Удалить
  3. >> у нас — системных программистов ...
    выход есть! он прямо за решёткой --->>>> C#

    ОтветитьУдалить
    Ответы
    1. По делу уже нечего сказать?

      Удалить
    2. Та надоело Вас уже учить праведному делу)

      Удалить
    3. "Я гуру, я учітєль! Несіть мені усі по три рубля! І мудрості я дам вам ..."

      Удалить
    4. #include "iostream"

      int main(void)
      {
      char* myString = new char[0];
      myString[0] = 'H';
      myString[1] = 'i';
      myString[2] = '!';
      myString[3] = 0;

      std::cout << myString << std::endl;
      return 0;
      }

      Такое чудо природы будет работать и работать будет потому, что все 32-битные компиляторы реально резервируют как минимум 4 байта под локальные переменные любых видов, т.к. это необходимо для выравнивания стека (и дизассемблерные листинги наглядно подтверждают это!).

      Удалить
    5. Так-с, коллеги, я в замешательстве. Во-первых: я был неправ, когда говорил, что к памяти выделенной векторным new для указателя на char нельзя достучаться для записи без heap corruption — можно, как в примере Дмитрия. Во-вторых: на самом деле доступно далеко не 4 байта и выравнивание тут не при чем — включал/отключал. Эмпирически я установил границу, до которой не возникает повреждения кучи - 24 байта и это мне совсем непонятно.
      И еще: любая форма delete (как векторная так и скалярная) вызванная для нашего указателя вызывает heap corruption, что ввело меня в заблуждение изначально.

      Удалить
    6. Ах этот дивный язык! Сколько ещё в нём скрыто загадок!

      Удалить
    7. Рябата!!! Какие загадки, на все есть исходники компилятора) Опять же, поведение абсолютно правильно и логично, вопрос только один "Зачем?", если есть C# 0=)

      /* Malloc implementation for multiple threads without lock contention.
      Copyright (C) 1996-2002, 2003, 2004 Free Software Foundation, Inc.
      This file is part of the GNU C Library.
      Contributed by Wolfram Gloger
      and Doug Lea , 2001.

      ...

      * Vital statistics:

      Supported pointer representation: 4 or 8 bytes
      Supported size_t representation: 4 or 8 bytes
      Note that size_t is allowed to be 4 bytes even if pointers are 8.
      You can adjust this by defining INTERNAL_SIZE_T

      Alignment: 2 * sizeof(size_t) (default)
      (i.e., 8 byte alignment with 4byte size_t). This suffices for
      nearly all current machines and C compilers. However, you can
      define MALLOC_ALIGNMENT to be wider than this if necessary.

      Minimum overhead per allocated chunk: 4 or 8 bytes
      Each malloced chunk has a hidden word of overhead holding size
      and status information.

      Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead)
      8-byte ptrs: 24/32 bytes (including, 4/8 overhead)

      When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte
      ptrs but 4 byte size) or 24 (for 8/8) additional bytes are
      needed; 4 (8) for a trailing size field and 8 (16) bytes for
      free list pointers. Thus, the minimum allocatable size is
      16/24/32 bytes.

      Even a request for zero bytes (i.e., malloc(0)) returns a
      pointer to something of the minimum allocatable size.

      The maximum overhead wastage (i.e., number of extra bytes
      allocated than were requested in malloc) is less than or equal
      to the minimum size, except for requests >= mmap_threshold that
      are serviced via mmap(), where the worst case wastage is 2 *
      sizeof(size_t) bytes plus the remainder from a system page (the
      minimal mmap unit); typically 4096 or 8192 bytes.

      Удалить
    8. Да какие там исходники компилятора! При чём тут GNU C library, если мы использовали MS Visual Studio 2010.

      Удалить
    9. При том, что исходников Visual Studio у нас нет, а посмотреть логику хочется) В любом случае как я предположил выше, для всех 32-битных компиляторов выделяется 4 байта, остальное зависит уже от конкретной реализации, которую я собственно и привёл)

      p.s. всё логично и правильно, спорить больше не о чем)

      Удалить
    10. Я угораю ;-)
      Какие 32-битные компиляторы? Ты сам читал, что выложил?
      Ото лишь бы что-то ляпнуть.

      Удалить
  4. че тут? читать надо?

    ОтветитьУдалить
    Ответы
    1. Конечно читай, правда толку от этого никакого.

      Удалить
    2. С первых строк можно было почувствовать, что статья написана холивара ради)

      Удалить
    3. Неужели? Ну-ка подробней, где там тема holly war, поднятая именно в ПОСТЕ?

      Удалить
  5. Ололо, пишите на Objective-C xD

    А по теме вспомнилась вот эта статья http://habrahabr.ru/post/137411/ про printf()

    Есть идея создать сервис Грабли.com на котором люди будут делиться граблями, на которые они наступают.

    Слон, инкубатор, половина.

    ОтветитьУдалить
  6. Каждый кулик своё болото хвалит. Чем лучше-то ваша поделка?!

    ОтветитьУдалить
  7. Платят больше) http://dou.ua/lenta/articles/developers-salaries-december-2011/

    ОтветитьУдалить
    Ответы
    1. Платят хоть и больше, геммороя тоже больше.
      Да и вообще по-моему хорошим инженерам на любой технологии хорошо платят. Если конечно умеешь себя выгодно продать.

      Удалить
    2. "Если конечно умеешь себя выгодно продать." — Что это значит в твоем понимании? Ты так часто повторяешь эту фразу, что просто нельзя не спросить, какой смысл ты в нее вкладываешь. Хорошим инженерам плохо платят на заводе, а инженеров в IT не так уж много - ремесленники в основном.

      Удалить