Share
Embed
Языки и методы программирования - лекция-10: строки в Си
Лекция курса "Языки и методы программирования"
Лекция-10: строки в Си
- Строки в Си: массивы байт char
- Строки-массивы на стеке и строки-константы
- Сегмент памяти для размещения строк-констант
- Роль модификатора const
- Просмотр сегментов памяти для запущенного приложения в ОС GNU/Linux
- Задания для самостоятельной работы
Anton
December 08, 2024
Transcript
31 slides
-
• Строка в Си — это массив символов char •
Один символ — 1 байт (октет, 8 бит) • Обычно кодировка ASCII • В конце массива завершающий невидимый 0 • Вас никто не заставляет его туда добавлять • Но при создании строк средствами языка это сделает для вас компилятор • И на него ориентируются стандартные библиотечные функции, работающие со строками • В том числе printf -
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); } -
> gcc prog-char-str.c > ./a.out Hooking grass Ошибка сегментирования (сделан
дамп памяти) • Первую строку (str1) получилось отредактировать как обычный массив • Вылетаем с ошибкой при попытке изменить значение внутри массива-строки, на который указывает str2 -
Замечание • Исходное значение строки-константы (str2) попадает в свой сегмент
на этапе компиляции и размещается в памяти в момент загрузки программы • Исходное значение изменяемой строки (str1) попадает на стек в процессе выполнения программы. Откуда оно берется, решает компилятор. • Например, компилятор x86 разместил исходное значение строки из примера прямо в сегменте инструкций (сегмент .text). • Компилятор mips разместил исходное значение в сегменте данных рядом с неизменяемыми строками-константами (сегмент .rodata) • Смотреть: «gcc -g» + «objdump -s» -
#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 -
> 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 -
#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); } -
> 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 влияет на сообщения компилятора, но не на размещение в памяти объявленных строк. -
Сегменты памяти для запущенного приложения • Запустить приложение в режиме
отладки или зациклить или поставить на ввод (чтобы не завершилось) • Узнать pid (process id — идентификатор процесса) > ps -A | grep a.out 26779 pts/4 00:00:43 a.out • Вывести сегменты памяти для приложения > cat /proc/26779/maps -
> 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] -
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. -
#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) { } } -
> 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 • и посмотрим на права доступа к сегментам -
addr(char* str2): 0x4006ba addr(const char* str2_1): 0x4006e1 • Попали в
сегмент «a.out» «r-xp» -
addr(char str1[]): 0x7fffed5837b0 addr(const char str1_1[]): 0x7fffed5837c0 • Попали в
сегмент «stack» «rw-p» -
Как видим • Изменяемые строки-массивы, действительно, попали на стек, помеченный
как «rw» (read/write) • Строки-константы, действительно, попали в сегмент, помеченный как «r» (read-only) • Модификатор const не влияет на выбор сегмента при размещении переменных -
Самостоятельно посмотрите • Стандартные функции для работы со строками: string.h.
Обращайте внимание, где они в качестве параметров принимают изменяемые строки, а где — строки- константы. -
Задание-0 • Объявить две строки, убрать у одной строки завершающий
ноль, при необходимости выйти за пределы этой строки, вызвать printf так, чтобы одним вызовом напечатались обе строки -
Задание-1 • Напишите программу, которая берет строку и печатает каждый
отдельный символ и его код ASCII в виде таблицы: 'символ-1' — код_ascii-1 'символ-2' — код_ascii-2 … • Исходную строку можно задать прямо в коде или считать с клавиатуры