Вопрос про массив строк и указатели

Тема в разделе "Arduino & Shields", создана пользователем DrProg, 1 июл 2015.

  1. DrProg

    DrProg Вечный нерд

    Поскольку ветку "программирование" так и не создали, спрошу здесь. Мне не до конца понятен механизм указателей, в частности в массивах.

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

    Код (Text):
    char* myStrings[]={"This is string 1", "This is string 2", "This is string 3",
    "This is string 4", "This is string 5","This is string 6"};

    void setup(){
    Serial.begin(9600);
    }

    void loop(){
    for (int i = 0; i < 6; i++){
      Serial.println(myStrings[i]);
      delay(500);
      }
    }
     
    Он занимает гораздо меньше памяти, чем массив String, и работает хорошо. Но все же не понятно каким образом. Например как указатели массива символов получаются именно на начало строки и как определяется конец? Расставляются автоматически? Выход за размерность при этом как то отслеживается или после 6 элемента просто начинается хаос?

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

    geher Гуру

    char* myStrings[]={"This is string 1", "This is string 2", "This is string 3",
    "This is string 4", "This is string 5","This is string 6"};
    Строго говоря это вообще ни разу не двумерный массив.
    Это массив указателей, т.е целых значений (если не ошибаюсь, 16 бит), представляющих из себя адреса в ОЗУ. Размерность массива при такой записи определяется количеством инициализирующих элементов.

    Каждая константная строка является последовательностью символов, занимающих в памяти последовательно идущие байты. Для индикации конца строки после последнего добавляется еще один символ, не входящий в строку, с кодом ноль ('\0'). На этот ноль в качестве завершения и ориентируются функции, работающие с классическими строками языка C, т.е с теми, доступ к которым определяется либо указателем char*, либо именем массива.
    Длина памяти, отведенной под хранение константной строки определяется количеством символов в ней плюс еще один символ под завершающий 0.

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

    Если в память по указателю - элементу данного массива писать, то можно заменить константную строку новым значением.
    Но при этом следует помнить, что результат выхода за границу (длину константной строки) испортит данные в ОЗУ, размещенные рядом с этой строкой, которые вообще говоря могут быть любыми, и будут являться, скорее всего, значением какой-то переменной или другой строковой константой.
    А если переписать только завершающий ноль, то функции, работающие с такой строкой, не смогут вовремя остановиться и получат на вход некорректную строку (будут продолжать до первого нулевого байта в памяти после начала строки).
    Если ноль вписать в позицию между началом и концом строки, то все функции, работающие с такой строкой будут полагать, что строка уменьшилась, хотя распределение памяти не изменится, и символы после данной позиции со свежевписанным нулем никуда не исчезнут.
     
    Tomasina нравится это.
  3. DrProg

    DrProg Вечный нерд

    Вот более менее проясняется. То есть компилятор сам доводит до ума строки добавляя нули и вычисляя длину, записывая адреса начал строк в массив?

    А если объявить массив, например, int* myInt [] = {1, 2, 3, 4}; он так же заполнит его указателями на эти числа в памяти?
     
  4. geher

    geher Гуру

    Если не ошибаюсь, нет. Подобная конструкция имеет смысл только для строк.
    На практике конструкция
    int* myInt [] = {1, 2, 3, 4};
    вызывает ругань компилятора на тему недопустимости присваивания целочисленной константы указателю (элементу массива указателей в инициализации сопоставляется числовая константа).
     
  5. DrProg

    DrProg Вечный нерд

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

    ИгорьК Гуру

    Читай орг.
    Почему исключение? Просто конструкция такая. Если определяешь указатель на массив - он должен получать именно указатели. Явление "This is string 1" есть строка. Она в языке С определяется, с одной стороны, как указатель на нее, а с другой - обрабатывается путем поиска нуля в конце буковок.
    И получается конструкция, когда массив содержит указатели (массив указателей), а эти указатели нацелены на начало каждой строки.
     
  7. DrProg

    DrProg Вечный нерд

    А почему с массивами других типов это не работает? Понятно, что смысла в массиве указателей на массив int нет, т.к. размеры элементов известны и равны, но сама суть указателей при этом не совсем непонятна.
     
  8. ИгорьК

    ИгорьК Гуру

    А где Вы здесь увидели массивы (других типов)? Здесь и со строками нет никаких массивов (во множественном числе).
    Этот массив - склад указателей на элементы. Каждый из указателей нацелен на начало строки, расположенной где-то. Система знает, что, чтобы работать с этим хозяйством, например каким-то элементом (строкой), нужно взять ее двухбайтный адрес из ОДНОМЕРНОГО массива, найти строку по этому адресу и читать до тех пор, пока не нападешь на цифру 0.
    Просто так сделано.
     
  9. Unixon

    Unixon Оракул Модератор

    Вот все же наоборот. Именно эта структура и называется двумерным массивом в C. Другое дело, что корректность структуры на совести программиста (в данном случае - равная длина всех строк).
     
  10. ИгорьК

    ИгорьК Гуру

    То есть с разной длиной строк это работать не будет?
     
  11. Unixon

    Unixon Оракул Модератор

    Почему, будет. С точки зрения языка С - это двумерный массив char. Но если строки разной длины, то это уже не совсем массив, но структура, позволяющая получать доступ к свои данным, как к элементам двумерного массива.
     
  12. ИгорьК

    ИгорьК Гуру

    Вот прямо сейчас сидел и проверял:
    Код (C):
    #include <iostream>

    using namespace std;

    int main()
    {
      char* myStrings[]={"This tring 1", "This is dhdstring 2", "This is string 3",
      "This isng 4", "This is string 5","Thiscbcvb is string 6"};

        int i;
        for (i = 0; i < 6; i++){
          cout << myStrings[i] << endl;
        }
        return 0;
    }
    Все работает. Строки разные.
    То есть Вы говорите, что в зависимости от длины строк С++ создает разные объекты? Чет не верится.
     
  13. Unixon

    Unixon Оракул Модератор

    А теперь попробуйте заполнить эту структуру как массив N*M элементов, где N - количество строк, а M - количество символов самой длинной строки.
     
  14. ИгорьК

    ИгорьК Гуру

    Извиняюсь, не въехал.
    Из Ваших слов ранее я сделал вывод, что Вы однозначно указываете, что все строки здесь должны быть одинаковой длинны. Это я не понял или Вы не удачно выразились?
    Указанная ТС конструкция - она именно такая, как и написана. Я не встречал других вариантов, поэтому, к сожалению, не могу понять о чем речь.
     
  15. DrProg

    DrProg Вечный нерд

    Прелесть данной конструкции в том, что длина элементов может быть разной. Все работает замечательно, но хотелось бы получше понять механизм. Пока понял лишь, что такая конструкция доводится до ума самим компилятором.
     
  16. Limoney

    Limoney Гик

    Указатель может указывать на функцию и на массив любого типа.
    Не имеет значения, какой массив одно или двумерный в память записывается линейно, только изменяется указатель.
     
  17. geher

    geher Гуру

    Меня учили, что двумерный массив в C - это массив, элементами которого являются другие массивы, а не указатели на эти самые другие массивы.
    Т.е. физически в памяти элементы массива следуют один за другим начиная с адреса на который указывает имя массива.
    А в приведенной конструкции по адресу, которое определяет имя массива, находятся именно адреса, а не сами символы.
    Это можно трактовать и как исключение, и как особенности правил.
    Суть в том, что строковая константа, записанная в двойных кавычках, в выражении имеет значение указателя на память, где и записаны символы, составляющие константу.
    Т.е. массив указателей корректно инициализируется указателями.
    Для числовых констант такой фокус не проходит, ибо их значением в выражении, в котором они участвуют, являются они сами, а не их адреса
     
  18. Unixon

    Unixon Оракул Модератор

    Для статических массивов это так, для динамических - нет. Вообще, со строками двумерный "типа массив" возникает в результате маленького фокуса с оператором "[]" и представлением строковых констант.
     
  19. Unixon

    Unixon Оракул Модератор

    Я не про это. Если вы начнете к массиву строк переменной длины обращаться как к настоящему "прямоугольному" массиву, то неизбежно выйдете за границы отведенной памяти.

    Пример:
    Код (Text):

    const int n = 2;
    const int m = 6;
    char* s[] = {"abc","012345"};

    void foo()
    {
      for (int i = 0; i < n; i++)
      {
       for (int j = 0; j < m; j++)
       {
        std::cout<<s[i][j];
       }
       std::cout<<std::endl;
      }
    }

    void bar()
    {
      for (int i = 0; i < n; i++)
      {
       for (int j = 0; j < m; j++)
       {
        s[i][j] = '0'+j;
       }
      }
    }
     
     
  20. ИгорьК

    ИгорьК Гуру

    Unixon, уважаемый, что я пропустил такого очень важного, что прочитать? Почему Вы обращаетесь к массиву как к двумерному? Почему m=6, а не 10, например?
    Всегда считал, что это одномерный массив, в который последовательно записаны адреса строк.
    Можем ли мы, для вашего случая, сделать затем (после определения массива) присваивание s[1] = "012345678"?