Как мне получить доступ к отдельному символу из массива строк в c? - PullRequest
7 голосов
/ 03 марта 2009

Просто пытаюсь понять, как обращаться к одному символу в массиве строк. Кроме того, это, конечно, позволит мне понять указатели на подписку подписчиков в целом. Если у меня есть char **a и я хочу получить 3-й символ 2-й строки, это работает: **((a+1)+2)? Похоже, что это должно ...

Ответы [ 6 ]

18 голосов
/ 03 марта 2009

Почти, но не совсем. Правильный ответ:

*((*(a+1))+2)

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

В качестве альтернативы это выражение:

a[1][2]

также сработает! .... и, возможно, предпочтительнее, потому что цель того, что вы пытаетесь сделать, более очевидна, а сама запись более лаконична. Эта форма может быть не сразу очевидна для новичков в языке, но они понимают, что причина, по которой работает запись массива, заключается в том, что в C операция индексации массива на самом деле является просто сокращением для эквивалентной операции указателя. то есть: * (a + x) совпадает с [x]. Таким образом, расширяя эту логику до исходного вопроса, есть две отдельные операции разыменования указателей, каскадно соединенные вместе, посредством чего выражение a [x] [y] эквивалентно общему виду * ((* (a + x)) + у).

3 голосов
/ 03 марта 2009

Вам не нужно использовать указатели.

int main (int argc, char ** argv) {

printf («Третий символ argv [1] равно [% c]. \ n ", argv [1] [2]);

}

Тогда:

$ ./main hello Третий символ argv [1] равно [l].

Это один и один.

Вы можете использовать указатели, если хотите ...

* (argv [1] +2)

или даже

* ((* (а + 1)) + 2) * 1 028 *

Как кто-то указал выше.

Это потому, что имена массивов являются указателями.

2 голосов
/ 07 июня 2015

The блестящее объяснение программирования на C в книге Hacking the art of эксплуатирование 2nd Edition Джона Эриксона, в которой обсуждаются указатели, строки, заслуживающие упоминания только для одного раздела объяснения программирования https://leaksource.files.wordpress.com/2014/08/hacking-the-art-of-exploitation.pdf.

Хотя на этот вопрос уже дан ответ, кто-то еще, желающий узнать больше, может найти следующие основные моменты из книги Эриксонса полезными для понимания структуры, стоящей за этим вопросом.

Headers

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

stdio.h - http://www.cplusplus.com/reference/cstdio/

stdlib.h - http://www.cplusplus.com/reference/cstdlib/

string.h - http://www.cplusplus.com/reference/cstring/

limit.h - http://www.cplusplus.com/reference/climits/

Функция

Примеры функций общего назначения, которые вы, вероятно, будете использовать.

malloc () - http://www.cplusplus.com/reference/cstdlib/malloc/

calloc () - http://www.cplusplus.com/reference/cstdlib/calloc/

strcpy () - http://www.cplusplus.com/reference/cstring/strcpy/

Память

" Память скомпилированной программы разделена на пять сегментов: текст, данные, bss, куча и стек. Каждый сегмент представляет собой специальную часть памяти, выделенную для определенной цели. Сегмент текста также иногда называется сегментом кода. Здесь собранные инструкции программы на машинном языке находятся".

" Выполнение инструкций в этом сегменте нелинейно, благодаря вышеупомянутым структурам и функциям управления высокого уровня, которые компилируют в переход, переход и инструкции вызова на ассемблере. Как программа выполняется, EIP устанавливается на первую инструкцию в текстовом сегменте. Затем процессор следует циклу выполнения, который выполняет следующее:"

" 1. Читает инструкцию, на которую указывает EIP "

" 2. Добавляет длину байта инструкции к EIP "

" 3. Выполняет инструкцию, прочитанную на шаге 1 "

" 4. Возвращается к шагу 1 "

" Иногда инструкция будет прыжком или инструкцией вызова, которая изменяет EIP на другой адрес памяти. Процессор не заботиться об изменении, потому что он ожидает, что выполнение будет нелинейным тем не мение. Если EIP изменяется на шаге 3, процессор просто вернется к шагу 1 и прочитает инструкцию, найденную по адресу того EIP, который был изменен на".

" Разрешение на запись отключено в текстовом сегменте, так как оно не используется для хранения переменных, только кода. Это не позволяет людям фактически изменять программный код; любая попытка записи в этот сегмент памяти приведет к программа для предупреждения пользователя о том, что случилось что-то плохое, и программа будет убит Другое преимущество этого сегмента, доступного только для чтения, заключается в том, что он могут быть разделены между различными копиями программы, что позволяет несколько Выполнение программы одновременно без проблем. Должно Также следует отметить, что этот сегмент памяти имеет фиксированный размер, так как ничего изменения в нем".

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

" Сегмент кучи - это сегмент памяти, который программист может напрямую контроль. Блоки памяти в этом сегменте могут быть выделены и использованы для все, что может понадобиться программисту Один заметный момент о куче сегмент означает, что он не имеет фиксированного размера, поэтому он может увеличиваться или уменьшаться по мере необходимости".

" Вся память в куче управляется алгоритмами распределителя и освобождения, которые соответственно резервируют область памяти в куче для использования и удаляют резервирования, чтобы позволить этой части памяти повторно использоваться для последующих резервирований. Куча будет расти и уменьшаться в зависимости от того, как много памяти зарезервировано для использования. Это означает, что программист использует кучу Функции выделения могут резервировать и освобождать память на лету. Рост куча движется вниз к старшим адресам памяти".

" Сегмент стека также имеет переменный размер и используется как временный блокнот для хранения локальных переменных функций и контекста во время вызовов функций. Это то, на что смотрит команда возврата в GDB. Когда программа вызывает функцию, это Функция будет иметь свой собственный набор переданных переменных, а код функции будет находиться в другом месте памяти в текстовом (или кодовом) сегменте. Так как контекст и EIP должны изменяться при вызове функции, стек используется для запоминания все переданные переменные, место, куда EIP должен вернуться после завершения функции, и все локальные переменные, используемые этой функцией. Вся эта информация хранится вместе в стеке в том, что вместе называется фреймом стека. содержит много кадров стека".

" В общих терминах информатики, стек - это абстрактная структура данных, которая часто используется. Она имеет порядок« первым пришел - последним вышел »(FILO) , что означает, что первый элемент, который помещается в стек, является последним элементом, который выходит из него. Думайте об этом как о наложении бус на кусок нити, у которого на одном конце есть узел - вы не сможете снять первый бус, пока не удалите все остальные бусины. Когда элемент помещается в стек, это называется выталкиванием, а когда элемент удаляется из стека, это называется popping".

" Как следует из названия, сегмент стека памяти, фактически, является структурой данных стека, которая содержит кадры стека. Регистр ESP используется для отслеживания адреса конца стека, который постоянно меняется по мере того, как элементы выталкиваются и выталкиваются из него. Так как это очень динамичное поведение, имеет смысл, что стек также не имеет фиксированного размера. В противоположность динамическому росту кучи, по мере изменения стека При увеличении размера он увеличивается в визуальном списке памяти к младшим адресам памяти".

" FILO-природа стека может показаться странной, но поскольку стек используется для сохранения контекста это очень полезно. Когда вызывается функция, несколько вещей помещаются в стек вместе в кадре стека. Регистр EBP - иногда называется указателем кадра (FP) или указателем локальной базы (LB) - используется для ссылки на локальные переменные функции в текущем кадре стека. Каждый кадр стека содержит параметры функции, ее локальные переменные и два указателя, которые необходимы для того, чтобы вернуть вещи такими, какими они были: указатель сохраненного кадра (SFP) и адрес возврата. SFP используется для восстановления EBP до его предыдущего значения и обратного адреса используется для восстановления EIP до следующей инструкции, найденной после вызова функции. Это восстанавливает функциональный контекст предыдущего стека рама ».

Строка

" В C массив - это просто список из n элементов определенного типа данных. Массив из 20 символов - это просто 20 смежных символов, расположенных в памяти. Массивы также называются буферами " .

#include <stdio.h>

int main()
{
    char str_a[20];
    str_a[0] = 'H';
    str_a[1] = 'e';
    str_a[2] = 'l';
    str_a[3] = 'l';
    str_a[4] = 'o';
    str_a[5] = ',';
    str_a[6] = ' ';
    str_a[7] = 'w';
    str_a[8] = 'o';
    str_a[9] = 'r';
    str_a[10] = 'l';
    str_a[11] = 'd';
    str_a[12] = '!';
    str_a[13] = '\n';
    str_a[14] = 0;
    printf(str_a);
}

" В предыдущей программе 20-элементный массив символов определяется как str_a, и каждый элемент массива записывается один за другим. Обратите внимание, что число начинается с 0, а не с 1. Также обратите внимание, что последний символ - это 0".

" (Этотакже называется нулевым байтом.) Массив символов был определен, поэтому для него выделено 20 байтов, но фактически используются только 12 из этих байтов. Программирование нулевого байта в конце используется как символ-разделитель, чтобы сообщить любой функции, имеющей дело со строкой, о необходимости остановить операции прямо здесь. Оставшиеся лишние байты являются просто мусором и будут игнорироваться. Если нулевой байт вставлен в пятый элемент массива символов, только функции Hello будут напечатаны функцией printf ()".

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

" Порядок аргументов функций похож на назначение синтаксиса сборки Intel вначале, а затем на источник. Программу char_array.c можно переписать с помощью strcpy (), чтобы выполнить то же самое с помощью библиотеки строк. Следующая версия программы char_array, показанной ниже, включает string.h, поскольку она использует строковую функцию".

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20];
    strcpy(str_a, "Hello, world!\n");
    printf(str_a);
}

Найти дополнительную информацию о строках C

http://www.cs.uic.edu/~jbell/CourseNotes/C_Programming/CharacterStrings.html

http://www.tutorialspoint.com/cprogramming/c_strings.htm

Указатели

" Регистр EIP - это указатель, который« указывает »на текущую инструкцию во время выполнения программы, содержащий адрес ее памяти. Идея указателей используется и в Си. Так как физическая память фактически не может быть перемещена информация в нем должна быть скопирована. Копирование больших кусков памяти для использования различными функциями или в разных местах может быть очень затратным в вычислительном отношении. Это также дорого с точки зрения памяти, поскольку пространство для новой целевой копии должно быть сохранены или распределены до того, как источник может быть скопирован. Указатели являются решением этой проблемы. Вместо копирования большого блока памяти гораздо проще передать адрес начала этого блока памяти".

" Указатели в C могут быть определены и использованы как любой другой тип переменной. Поскольку память в архитектуре x86 использует 32-разрядную адресацию, указатели также имеют размер 32 бита (4 байта). Указатели определяются путем добавления звездочка (*) к имени переменной. Вместо определения переменной этого типа указатель определяется как нечто, указывающее на данные этого типа. Программа pointer.c является примером указателя, используемого с данными типа char тип, размер которого составляет всего 1 байт".

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20]; // A 20-element character array
    char *pointer; // A pointer, meant for a character array
    char *pointer2; // And yet another one
    strcpy(str_a, "Hello, world!\n");
    pointer = str_a; // Set the first pointer to the start of the array.
    printf(pointer);
    pointer2 = pointer + 2; // Set the second one 2 bytes further in.
    printf(pointer2); // Print it.
    strcpy(pointer2, "y you guys!\n"); // Copy into that spot.
    printf(pointer); // Print again.
}

" Как показывают комментарии в коде, первый указатель устанавливается в начале массива символов. Когда на массив символов ссылаются так, это фактически сам указатель. Вот как этот буфер был передается как указатель на функции printf () и strcpy () ранее. Второй указатель устанавливается на адрес первого указателя плюс два, а затем печатаются некоторые вещи (как показано в выводе ниже)".

reader@hacking:~/booksrc $ gcc -o pointer pointer.c
reader@hacking:~/booksrc $ ./pointer
Hello, world!
llo, world!
Hey you guys!
reader@hacking:~/booksrc $

" Оператор address-of часто используется вместе с указателями, так как указатели содержат адреса памяти. Программа addressof.c демонстрирует оператор адреса, используемый для помещения адреса целочисленной переменной в указатель. Эта строка выделена жирным шрифтом ниже".

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // put the address of int_var into int_ptr
}

" Для использования с указателями существует дополнительный унарный оператор, называемый оператором разыменования. Этот оператор будет возвращать данные, найденные в адресе, на который указывает указатель, вместо самого адреса. Он принимает форму звездочки. перед именем переменной, аналогично объявлению указателя. Еще раз, оператор разыменования существует как в GDB, так и в C".

" Несколько дополнений к коду addressof.c (показан в addressof2.c) продемонстрировать все эти концепции. Добавленные функции printf () используют формат пунктметров, которые я объясню в следующем разделе. А пока просто сфокусируйтесь на выводе программ".

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // Put the address of int_var into int_ptr.
    printf("int_ptr = 0x%08x\n", int_ptr);
    printf("&int_ptr = 0x%08x\n", &int_ptr);
    printf("*int_ptr = 0x%08x\n\n", *int_ptr);
    printf("int_var is located at 0x%08x and contains %d\n", &int_var, int_var);
    printf("int_ptr is located at 0x%08x, contains 0x%08x, and points to %d\n\n", &int_ptr, int_ptr, *int_ptr);
}

" Когда унарные операторы используются с указателями, оператор address-of можно рассматривать как движущийся назад, тогда как оператор разыменования перемещается вперед в направлении, на которое указывает указатель ".

Подробнее об указателях и распределении памяти

Профессор Дэн Хиршберг, факультет компьютерных наук, Университет Калифорнии, по памяти компьютера

http://cslibrary.stanford.edu/106/

http://www.programiz.com/c-programming/c-dynamic-memory-allocation

Массивы

Вот простое руководство по многомерным массивам от парня по имени Алекс Аллен, доступное здесь http://www.cprogramming.com/tutorial/c/lesson8.html

Информация о массивах парня по имени Тодд Гибсон доступна здесь http://www.augustcouncil.com/~tgibson/tutorial/arr.html

Итерация массива

#include <stdio.h>

int main() 
{

    int i;
    char char_array[5] = {'a', 'b', 'c', 'd', 'e'};
    int int_array[5] = {1, 2, 3, 4, 5};
    char *char_pointer;
    int *int_pointer;
    char_pointer = char_array;
    int_pointer = int_array;

    for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.
        printf("[integer pointer] points to %p, which contains the integer %d\n", int_pointer, *int_pointer);
        int_pointer = int_pointer + 1;
    }

    for(i=0; i < 5; i++) { // Iterate through the char array with the char_pointer.
        printf("[char pointer] points to %p, which contains the char '%c'\n", char_pointer, *char_pointer);
        char_pointer = char_pointer + 1;
    }

}

Связанные списки и массивы

Массивы - не единственная доступная опция, информация о связанном списке.

http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_linklist.aspx

Заключение

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

2 голосов
/ 03 марта 2009

Цитата из википедии статья на C указателях -

В C индексация массива формально определяется в терминах арифметики указателей; то есть, спецификация языка требует, чтобы массив [i] был эквивалентен * (массив + i). Таким образом, в C массивы можно рассматривать как указатели на последовательные области памяти (без пробелов), и синтаксис для доступа к массивам идентичен для того, который может использоваться для разыменования указатели. Например, массив может быть объявлен и использован следующим образом:

int array[5];      /* Declares 5 contiguous (per Plauger Standard C 1992) integers */
int *ptr = array;  /* Arrays can be used as pointers */
ptr[0] = 1;        /* Pointers can be indexed with array syntax */
*(array + 1) = 2;  /* Arrays can be dereferenced with pointer syntax */

Итак, в ответ на ваш вопрос - yes , указатели на указатели могут использоваться как массив без какого-либо другого объявления вообще!

2 голосов
/ 03 марта 2009

Iirc, строка на самом деле является массивом символов, поэтому это должно работать:

a[1][2]
1 голос
/ 03 марта 2009

Попробуйте a[1][2]. Или *(*(a+1)+2).

По сути, ссылки на массивы являются синтаксическим сахаром для разыменования указателей. a [2] - это то же самое, что и + 2, а также то же самое, что и 2 [a] (если вы действительно хотите нечитаемый код). Массив строк такой же, как двойной указатель. Таким образом, вы можете извлечь вторую строку, используя [1] или *(a+1). Затем вы можете найти третий символ в этой строке (пока назовите его «b») с помощью b [2] или *(b + 2). Подставляя исходную вторую строку для 'b', мы получаем либо [1] [2], либо *(*(a+1)+2).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...