Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Языки и методы программирования - лекция-10: ст...

Anton
December 08, 2024

Языки и методы программирования - лекция-10: строки в Си

Лекция курса "Языки и методы программирования"
Лекция-10: строки в Си
- Строки в Си: массивы байт char
- Строки-массивы на стеке и строки-константы
- Сегмент памяти для размещения строк-констант
- Роль модификатора const
- Просмотр сегментов памяти для запущенного приложения в ОС GNU/Linux
- Задания для самостоятельной работы

Anton

December 08, 2024
Tweet

More Decks by Anton

Other Decks in Education

Transcript

  1. • Строка в Си — это массив символов char •

    Один символ — 1 байт (октет, 8 бит) • Обычно кодировка ASCII • В конце массива завершающий невидимый 0 • Вас никто не заставляет его туда добавлять • Но при создании строк средствами языка это сделает для вас компилятор • И на него ориентируются стандартные библиотечные функции, работающие со строками • В том числе printf
  2. include <stdio.h> int main(void) { // изменяемый массив на стеке

    char str1[] = "Looking glass"; str1[0] = 'H'; str1[9] = 'r'; printf("%s\n", str1); // неизменяемый массив (строка-константа) в памяти данных char* str2 = "Glassing look"; str2[0] = 'B'; str2[1] = 'r'; str2[9] = 'h'; printf("%s\n", str2); }
  3. > gcc prog-char-str.c > ./a.out Hooking grass Ошибка сегментирования (сделан

    дамп памяти) • Первую строку (str1) получилось отредактировать как обычный массив • Вылетаем с ошибкой при попытке изменить значение внутри массива-строки, на который указывает str2
  4. Замечание • Исходное значение строки-константы (str2) попадает в свой сегмент

    на этапе компиляции и размещается в памяти в момент загрузки программы • Исходное значение изменяемой строки (str1) попадает на стек в процессе выполнения программы. Откуда оно берется, решает компилятор. • Например, компилятор x86 разместил исходное значение строки из примера прямо в сегменте инструкций (сегмент .text). • Компилятор mips разместил исходное значение в сегменте данных рядом с неизменяемыми строками-константами (сегмент .rodata) • Смотреть: «gcc -g» + «objdump -s»
  5. #include <stdio.h> int main(void) { // неизменяемый массив в памяти

    данных const char* str2 = "Glassing look"; // error: assignment of read-only location ‘*(str3 + 2u)’ str2[0] = 'B'; str2[1] = 'r'; str2[9] = 'h'; printf("%s\n", str2); } Поправим код: добавим модификатор const
  6. > gcc prog-char-str-mod1.c prog-char-str-mod1.c: In function ‘main’: prog-char-str-mod1.c:9:13: error: assignment

    of read- only location ‘*str2’ str2[0] = 'B'; ^ • Теперь ошибка компиляции (не дожидаясь запуска): попытка изменить содержимое строки, указатель на которую помечен как const
  7. #include <stdio.h> int main(void) { // неизменяемый массив в памяти

    данных const char* str2 = "Glassing look"; // сменим указатель char* str3 = str2; // Brassing hook // Ошибка сегментирования (сделан дамп памяти) str3[0] = 'B'; str3[1] = 'r'; str3[9] = 'h'; printf("%s\n", str3); }
  8. > gcc prog-char-str-mod2.c prog-char-str-mod2.c: In function ‘main’: prog-char-str-mod2.c:15:18: warning: initialization

    discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] char* str3 = str2; ^ > ./a.out Ошибка сегментирования (сделан дамп памяти) • На этапе компиляции не ошибка, а предупреждение — исполняемый файл будет создан • На этапе запуска ожидаемо вылетаем с ошибкой (причина всё та же) • Вывод: строки, объявленные как «char*» и как «const char*» размещаются в сегменте данных, доступном только для чтения. Модификатор const влияет на сообщения компилятора, но не на размещение в памяти объявленных строк.
  9. Сегменты памяти для запущенного приложения • Запустить приложение в режиме

    отладки или зациклить или поставить на ввод (чтобы не завершилось) • Узнать pid (process id — идентификатор процесса) > ps -A | grep a.out 26779 pts/4 00:00:43 a.out • Вывести сегменты памяти для приложения > cat /proc/26779/maps
  10. > ps -A | grep a.out 26779 pts/4 00:00:12 a.out

    > cat /proc/26779/maps 00400000-00401000 r-xp 00000000 103:05 4590897 /home/user/prog-test/a.out 00600000-00601000 r--p 00000000 103:05 4590897 /home/user/prog-test/a.out 00601000-00602000 rw-p 00001000 103:05 4590897 /home/user/prog-test/a.out 01d70000-01d91000 rw-p 00000000 00:00 0 [heap] 7f362b970000-7f362bb30000 r-xp 00000000 103:02 2113484 /lib/x86_64-linux-gnu/libc-2.23.so 7f362bb30000-7f362bd30000 ---p 001c0000 103:02 2113484 /lib/x86_64-linux-gnu/libc-2.23.so 7f362bd30000-7f362bd34000 r--p 001c0000 103:02 2113484 /lib/x86_64-linux-gnu/libc-2.23.so 7f362bd34000-7f362bd36000 rw-p 001c4000 103:02 2113484 /lib/x86_64-linux-gnu/libc-2.23.so 7f362bd36000-7f362bd3a000 rw-p 00000000 00:00 0 7f362bd3a000-7f362bd60000 r-xp 00000000 103:02 2113469 /lib/x86_64-linux-gnu/ld-2.23.so 7f362bf0b000-7f362bf0e000 rw-p 00000000 00:00 0 7f362bf5f000-7f362bf60000 r--p 00025000 103:02 2113469 /lib/x86_64-linux-gnu/ld-2.23.so 7f362bf60000-7f362bf61000 rw-p 00026000 103:02 2113469 /lib/x86_64-linux-gnu/ld-2.23.so 7f362bf61000-7f362bf62000 rw-p 00000000 00:00 0 7fffed565000-7fffed586000 rw-p 00000000 00:00 0 [stack] 7fffed589000-7fffed58c000 r--p 00000000 00:00 0 [vvar] 7fffed58c000-7fffed58e000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
  11. Understanding Linux /proc/id/maps stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps#1401595 • address - This is the

    starting and ending address of the region in the process's address space • permissions - This describes how pages in the region can be accessed. There are four different permissions: read, write, execute, and shared. If read/write/execute are disabled, a - will appear instead of the r/w/x. If a region is not shared, it is private, so a p will appear instead of an s. If the process attempts to access memory in a way that is not permitted, a segmentation fault is generated. Permissions can be changed using the mprotect system call. • offset - If the region was mapped from a file (using mmap), this is the offset in the file where the mapping begins. If the memory was not mapped from a file, it's just 0. • device - If the region was mapped from a file, this is the major and minor device number (in hex) where the file lives. • inode - If the region was mapped from a file, this is the file number. • pathname - If the region was mapped from a file, this is the name of the file. This field is blank for anonymous mapped regions. There are also special regions with names like [heap], [stack], or [vdso]. [vdso] stands for virtual dynamic shared object. It's used by system calls to switch to kernel mode.
  12. #include <stdio.h> int main(void) { // изменяемый массив на стеке

    char str1[] = "Looking glass"; printf("addr(char str1[]): 0x%lx\n", (unsigned long)str1); const char str1_1[] = "const Looking glass"; printf("addr(const char str1_1[]): 0x%lx\n", (unsigned long)str1_1); // неизменяемый массив в памяти данных char* str2 = "Glassing look"; printf("addr(char* str2): 0x%lx\n", (unsigned long)str2); const char* str2_1 = "const Glassing look"; printf("addr(const char* str2_1): 0x%lx\n", (unsigned long)str2_1); // зациклить, чтобы приложение повисело while(1) { } }
  13. > gcc prog-char-str-mem-addr.c > ./a.out addr(char str1[]): 0x7fffed5837b0 addr(const char

    str1_1[]): 0x7fffed5837c0 addr(char* str2): 0x4006ba addr(const char* str2_1): 0x4006e1 • Обратим внимание на адреса • Соотнесем их с диапазонами адресов сегментов памяти в таблице maps • и посмотрим на права доступа к сегментам
  14. Как видим • Изменяемые строки-массивы, действительно, попали на стек, помеченный

    как «rw» (read/write) • Строки-константы, действительно, попали в сегмент, помеченный как «r» (read-only) • Модификатор const не влияет на выбор сегмента при размещении переменных
  15. Самостоятельно посмотрите • Стандартные функции для работы со строками: string.h.

    Обращайте внимание, где они в качестве параметров принимают изменяемые строки, а где — строки- константы.
  16. Задание-0 • Объявить две строки, убрать у одной строки завершающий

    ноль, при необходимости выйти за пределы этой строки, вызвать printf так, чтобы одним вызовом напечатались обе строки
  17. Задание-1 • Напишите программу, которая берет строку и печатает каждый

    отдельный символ и его код ASCII в виде таблицы: 'символ-1' — код_ascii-1 'символ-2' — код_ascii-2 … • Исходную строку можно задать прямо в коде или считать с клавиатуры