Вычисление математического выражения из строки
во входном файле (in.txt) хранятся простые арифметические выражения, например:
5 + 7
3 — 4
2 * 2
2 / 5
в выражениях встречаются только операции сложения, вычитания, умножения и деления. целые числа по модулю не больше 10000. нужно вычислить значения выражений и записать в файл results.txt в столбик. Сам я не очень шарю, и написал примерно следующее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#include #include int main(void) { FILE *fin, *fout; fin = fopen("in.txt", "r"); fout = fopen("results.txt", "w"); int a; int b; float c; char sign; char *str; while(str != EOF) { fscanf("%d %c %d", &a, &sign, &b); switch(sign) { case '+': fprintf(fout, "%d\n", a + b); break; case '-': fprintf(fout, "%d\n", a - b); break; case '*': fprintf(fout, "%d\n", a * b); break; case '/': c = (float)a / b; fprintf(fout, "%f\n", c); break; } } fclose(fin); fclose(fout); return 0; }
Программа не работает, прошу указать ошибки
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Вычисление математического выражения
Не получается создать правильный код: Разработать программу для вычисления математического.
Вычисление математического выражения
Здравия. Прошу прощения за очередную стремную тему от ньюфага, но я совершенно не умею.
Вычисление математического выражения
Помогите реализовать математическое выражение на языке программирования PHP.
Вычисление математического выражения b= s*r*(g-l)
Помогите правильно написать код для этой формулы b= s*r * (g-l) ;b= s*r * (g-l) .MODEL SMALL.
136 / 90 / 48
Регистрация: 16.08.2016
Сообщений: 357
Попробуйте в условии цикла while написать (!feof(fin))
И в fscanf у вас не указано из какого файла читать
1021 / 561 / 185
Регистрация: 18.08.2013
Сообщений: 2,026
Записей в блоге: 2
Кликните здесь для просмотра всего текста
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include void initOperations(double (*op[])(double, double)); int main(void) { double (*op[6])(double, double); initOperations(op); FILE *inp = fopen("in", "r"); FILE *res = fopen("results", "w"); int a, b; char oper; while (!feof(inp)) { fscanf(inp, "%d %c %d\n", &a, &oper, &b); fprintf(res, "%.2f\n", op[oper - '*'](a, b)); } return 0; } double sum(double a, double b) { return a + b; } double subtract(double a, double b) { return a - b; } double multiply(double a, double b) { return a * b; } double divide(double a, double b) { return a / b; } void initOperations(double (*op[])(double, double)) { op['*' - '*'] = multiply; op['+' - '*'] = sum; op['-' - '*'] = subtract; op['/' - '*'] = divide; }
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Вычисление математического выражения.
Я ищу компонент (или функцию), который бы принимал математическое выражение в качестве строки.
Вычисление значения математического выражения
Можете написать код для вычисления следующего выражения: y = y1 — y2 , если: 1) y1 =.
Вычисление простого математического выражения
Здравствуйте. Помогите пожалуйста написать программу для вычисления вот такого выражения. Паскаль.
Обработка и вычисление математического выражения
Доброе время суток. Занимаюсь реализацией генетических алгоритмов оптимизации функций. Возникла.
Строки в языке C
Строка — это последовательность ASCII или UNICODE символов.
Строки в C, как и в большинстве языков программирования высокого уровня рассматриваются как отдельный тип, входящий в систему базовых типов языка. Так как язык C по своему происхождению является языком системного программирования, то строковый тип данных в C как таковой отсутствует, а в качестве строк в С используются обычные массивы символов.
Исторически сложилось два представления формата строк:
- формат ANSI;
- cтроки с завершающим нулем (используется в C).
Формат ANSI устанавливает, что значением первой позиции в строке является ее длина, а затем следуют сами символы строки. Например, представление строки «Моя строка!» будет следующим:
11 ‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’
В строках с завершающим нулем, значащие символы строки указываются с первой позиции, а признаком завершения строки является значение ноль. Представление рассмотренной ранее строки в этом формате имеет вид:
‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’ 0
Объявление строк в C
Строки реализуются посредством массивов символов. Поэтому объявление ASCII строки имеет следующий синтаксис:
char имя[длина];
Объявление строки в С имеет тот же синтаксис, что и объявление одномерного символьного массива. Длина строки должна представлять собой целочисленное значение (в стандарте C89 – константа, в стандарте C99 может быть выражением). Длина строки указывается с учетом одного символа на хранение завершающего нуля, поэтому максимальное количество значащих символов в строке на единицу меньше ее длины. Например, строка может содержать максимально двадцать символов, если объявлена следующим образом:
char str[21]; Инициализация строки в С осуществляется при ее объявлении, используя следующий синтаксис:
char str[длина] = строковый литерал;
Строковый литерал – строка ASCII символов заключенных в двойные кавычки. Примеры объявления строк с инициализацией:
char str1[20] = «Введите значение: «, str2[20] = «»;
const char message[] = «Сообщение об ошибке!»;
Работа со строками в С
Так как строки на языке С являются массивами символов, то к любому символу строки можно обратиться по его индексу. Для этого используется синтаксис обращения к элементу массива, поэтому первый символ в строке имеет индекс ноль. Например, в следующем фрагменте программы в строке str осуществляется замена всех символов ‘a’ на символы ‘A’ и наоборот.
for(int i = 0; str[i] != 0; i++)
if (str[i] == ‘a’) str[i] = ‘A’;
else if (str[i] == ‘A’) str[i] = ‘a’;
>
Массивы строк в С
Объявление массивов строк в языке С также возможно. Для этого используются двумерные массивы символов, что имеет следующий синтаксис:
char имя[количество][длина];
Первым размером матрицы указывается количество строк в массиве, а вторым – максимальная (с учетом завершающего нуля) длина каждой строки. Например, объявление массива из пяти строк максимальной длиной 30 значащих символов будет иметь вид:
При объявлении массивов строк можно производить инициализацию:
char имя[количество][длина] = ;
Число строковых литералов должно быть меньше или равно количеству строк в массиве. Если число строковых литералов меньше размера массива, то все остальные элементы инициализируются пустыми строками. Длина каждого строкового литерала должна быть строго меньше значения длины строки (для записи завершающего нуля).
char days[12][10] = <
«Январь», «Февраль», «Март», ”Апрель», «Май»,
«Июнь», «Июль», «Август», «Сентябрь»,»Октябрь»,
«Ноябрь», «Декабрь»
>;
При объявлении массивов строк с инициализацией допускается не указывать количество строк в квадратных скобках. В таком случае, количество строк в массиве будет определено автоматически по числу инициализирующих строковых литералов.
Например, массив из семи строк:
char days[][12] = <
«Понедельник», «Вторник», «Среда», «Четверг»,
«Пятница», «Суббота», «Воскресенье»
>;
Функции для работы со строками в С
Все библиотечные функции, предназначенные для работы со строками, можно разделить на три группы:
- ввод и вывод строк;
- преобразование строк;
- обработка строк.
Ввод и вывод строк в С
Для ввода и вывода строковой информации можно использовать функции форматированного ввода и вывода (printf и scanf). Для этого в строке формата при вводе или выводе строковой переменной необходимо указать спецификатор типа %s. Например, ввод и последующий вывод строковой переменной будет иметь вид:
char str[31] = «»;
printf(«Введите строку: «);
scanf(«%30s”,str);
printf(«Вы ввели: %s”,str);
Недостатком функции scanf при вводе строковых данных является то, что символами разделителями данной функции являются:
- перевод строки,
- табуляция;
- пробел.
Поэтому, используя данную функцию невозможно ввести строку, содержащую несколько слов, разделенных пробелами или табуляциями. Например, если в предыдущей программе пользователь введет строку: «Сообщение из нескольких слов», то на экране будет выведено только «Сообщение».
Для ввода и вывода строк в библиотеке stdio.h содержатся специализированные функции gets и puts.
Функция gets предназначена для ввода строк и имеет следующий заголовок:
char * gets(char *buffer);
Между тем использовать функцию gets категорически не рекомендуется, ввиду того, что она не контролирует выход за границу строки, что может произвести к ошибкам. Вместо нее используется функция fgets с тремя параметрами:
char * fgets(char * buffer, int size, FILE * stream);
где buffer — строка для записи результата, size — максимальное количество байт, которое запишет функция fgets, stream — файловый объект для чтения данных, для чтения с клавиатуры нужно указать stdin. Эта функция читает символы со стандартного ввода, пока не считает n — 1 символ или символ конца строки, потом запишет считанные символы в строку и добавит нулевой символ. При этом функция fgets записывает в том символ конца строки в данную строку, что нужно учитывать.
Функция puts предназначена для вывода строк и имеет следующий заголовок:
int puts(const char *string);
Простейшая программа: ввод и вывод строки с использованием функций fgets и puts будет иметь вид:
char str[102] = «»;
printf(«Введите строку: «);
fgets(str, 102, stdin);
printf(«Вы ввели: «);
puts(str);
Для считывания одного символа можно использовать функцию fgetc(FILE * stream) . Она считывает один символ и возвращает значение этого символа, преобразованное к типу int, если же считывание не удалось, то возвращается специальная константа EOF, равная -1. Функция возвращает значение -1 для того, чтобы можно было обрабатывать ситуацию конца файла, посимвольное чтение до конца файла можно реализовать следующим образом:
int c;
while ((c = fgetc(stdin)) != EOF) // Обработка символа
>
Для вывода одного символа можно использовать функцию int fputc(int c, FILE *stream); .
Помимо функций ввода и вывода в потоки в библиотеке stdio.h присутствуют функции форматированного ввода и вывода в строки. Функция форматированного ввода из строки имеет следующий заголовок:
int sscanf(const char * restrict buffer, const char * restrict string, [address] . );
Функции форматированного вывода в строку имеют следующие заголовки:
int sprintf(char * restrict buffer,
const char * restrict format, [argument] . );
int snprintf(char * restrict buffer, size_t maxsize,
const char * restrict format, [argument] . );
Преобразование строк
В С для преобразования строк, содержащих числа, в численные значения в библиотеке stdlib.h
предусмотрен следующий набор функций:
double atof(const char *string); // преобразование строки в число типа double
int atoi(const char *string); // преобразование строки в число типа int
long int atol(const char *string); // преобразование строки в число типа long int
long long int atoll(const char *string); // преобразование строки в число типа long long int
Корректное представление вещественного числа в текстовой строке должно удовлетворять формату:
После символов E, e указывается порядок числа. Корректное представление целого числа в текстовой строке должно удовлетворять формату:
Помимо приведенных выше функций в библиотеке stdlib.h доступны также следующие функции преобразования строк в вещественные числа:
float strtof(const char * restrict string, char ** restrict endptr);
double strtod(const char * restrict string, char ** restrict endptr);
long double strtold(const char * restrict string,char ** restrict endptr);
Аналогичные функции присутствуют и для преобразования строк в целочисленные значения:
long int strtol(const char * restrict string, char ** restrict endptr, int base);
unsigned long strtoul(const char * restrict string,
char ** restrict endptr, int base);
long long int strtoll(const char * restrict string,
char ** restrict endptr, int base);
unsigned long long strtoull(const char * restrict string,char ** restrict endptr, int base);
Функции обратного преобразования (численные значения в строки) в библиотеке stdlib.h присутствуют, но они не регламентированы стандартом, и рассматриваться не будут. Для преобразования численных значений в строковые наиболее удобно использовать функции sprintf и snprintf.
Обработка строк
В библиотеке string.h содержаться функции для различных действий над строками.
Функция вычисления длины строки:
size_t strlen(const char *string);
char str[] = «1234»;
int n = strlen(str); //n == 4
Функции копирования строк:
char * strcpy(char * restrict dst, const char * restrict src);
char * strncpy(char * restrict dst, const char * restrict src, size_t num);
Функции сравнения строк:
int strcmp(const char *string1, const char *string2);
int strncmp(const char *string1, const char *string2,size_t num);
Функции осуществляют сравнение строк по алфавиту и возвращают:
положительное значение – если string1 больше string2;
отрицательное значение – если string1 меньше string2;
нулевое значение – если string1 совпадает с string2;
Функции объединения (конкатенации) строк:
char * strcat(char * restrict dst, const char * restrict src);
char * strncat(char * restrict dst, const char * restrict src, size_t num);
Функции поиска символа в строке:
char * strchr(const char *string, int c);
char * strrchr(const char *string, int c);
Функция поиска строки в строке:
char * strstr(const char *str, const char *substr);
char str[] = «Строка для поиска»;
char *str1 = strstr(str,»для»); //str1 == «для поиска»
Функция поиска первого символа в строке из заданного набора символов:
size_t strcspn(const char *str, const char *charset);
Функции поиска первого символа в строке не принадлежащему заданному набору символов:
size_t strspn(const char *str, const char *charset);
Функции поиска первого символа в строке из заданного набора символов:
char * strpbrk(const char *str, const char *charset);
Функция поиска следующего литерала в строке:
char * strtok(char * restrict string, const char * restrict charset);
Вычисление выражения, заданного в виде строки
Добрый день. Нужна помощь с задачкой:
Дано строку типа «X + Y», где X и Y — некоторые числа, + — операция, которую нужно над ними выполнить. Необходимо выделить из строки эти числа, знак операции и вывести результат суммирования. Перед и после «+» может стоять пробел.
Туплю на считывании: хочу считать значение до +, получаю же «+7» вместо «1» (ввожу «1+7»), причем когда встречается в строке пробел — программа не работает. Помогите пожалуйста
Вот что пробовал писать:
1 2 3 4 5 6
char ryad[10]; char roz[]="+ -"; char*p1,*pr; for (p1=ryad;*p1!='+';p1++) gets(p1); puts(p1);
Лучшие ответы ( 2 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Вычисление выражения, заданного в виде строки
Здраствуйте всем!Немогу найти решение задачу таковую:Пользователь вписивает пример,уравнение в.
Вычисление арифметического выражения заданного в виде строки
Собственно вопрос в названии: возможно ли преобразовать строку с оператором в оператор? string s =.
Вычисление значения выражения заданного в виде строки
Помогите с программой, нужно:Дона строка которая содержит натуральные числа, знаки четырех.
Вычисление значения выражения, заданного в виде строки
Ребята, подскажите, пожалуйста. Как можно решить допустим такой пример в строке? string.
54 / 54 / 47
Регистрация: 16.03.2014
Сообщений: 110
Записей в блоге: 2
Ну это и понятно.
После операции присвоения p1 указывает на первый элемент массива ryad[10]. После операции инкремента, применённого к указателю p1, он уже указывет на второй элемент массива ryad[10]. Поэтому и выводится на печать «+7».
Если попробуешь puts(ryad), то на печать выйдет вся строка «1+7»
Регистрация: 07.05.2014
Сообщений: 101
Пробовал puts(ryad) и тд, в итоге получал ВСЕ кроме X. И вот не могу правильно написать чтобы считало именно его. А потом ведь надо считать и знак, и Y, а сам то на первом считывании час сижу
54 / 54 / 47
Регистрация: 16.03.2014
Сообщений: 110
Записей в блоге: 2
Сообщение от Nescafe32
for (p1=ryad;*p1!='+';p1++) gets(p1);
Вобще-то это неправильно. Попробуй ввести «21+23» и вывести на печать rayd.
Регистрация: 07.05.2014
Сообщений: 101
Вижу. Не хочет принимать. Подскажите тогда как верно записать цикл
54 / 54 / 47
Регистрация: 16.03.2014
Сообщений: 110
Записей в блоге: 2
Надо задачу разбить на подзадачи:
1. Считать строку в буфер.
2. Выделить из буфера первое число и сохранить его.
3. Выделить из буфера операцию, необходимую для выполнения над числами.
4. Выделить из буфера второе число и сохранить его.
5. Произвести операцию над числами.
6. Вывести результат на печать.
Регистрация: 07.05.2014
Сообщений: 101
Алгоритм я написал как только прочитал задачу, совпадает с Вашим. А на практике затуп
54 / 54 / 47
Регистрация: 16.03.2014
Сообщений: 110
Записей в блоге: 2
Вообще если зашёл в тупик, то лучше отложить задачу в строну и пойти и выпить чашечку чая (И В ЭТОТ МОМЕНТ ДУМАТЬ ТОЛЬКО О ЧАЕ). Когда ум расслабляется, то ответ приходит сам собой. Любая пришедшая идея стоит реализации на практике, даже если она не принесла результат. Это просто удлинит процесс решения задачи, но повысит уровень вашего знания.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
#include #include #include #include char *get_num(char *src, char *dst) { bool is_num = false; while(1) { if(false == is_num) { if(!isspace(*src) && isdigit(*src)) { is_num = true; *dst++ = *src++; } else { ++src; } } else { if(isdigit(*src)) { *dst++ = *src++; } else { break; } } } *dst = '\0'; return src; } char *get_op(char *src, char *op) { while(1) { if(!isspace(*src)) { if(*src == '+') { *op = *src++; break; } } ++src; } return src; } int main(int argc, char *argv[]) { int SIZE_BUF = 10; char buf[SIZE_BUF]; char num1[SIZE_BUF]; char num2[SIZE_BUF]; char op; // . fgets(buf, SIZE_BUF, stdin); char *tmp = get_num(buf, num1); tmp = get_op(tmp, &op); tmp = get_num(tmp, num2); fprintf(stdout, "num1 = %s; op = %c; num2 = %s;\n", num1, op, num2); return EXIT_SUCCESS; }
Добавлено через 6 минут
Когда расслабляешься лучше ни о чём не думать — даже о чае не думать — вообще НИ О ЧЁМ НЕ ДУМАТЬ.
Регистрация: 07.05.2014
Сообщений: 101
Спасибо. Довольно непонятный вариант, но буду разбираться
Добавлено через 7 часов 34 минуты
Не вышло разобраться. Нужно вариант попроще
54 / 54 / 47
Регистрация: 16.03.2014
Сообщений: 110
Записей в блоге: 2
Сообщение было отмечено Nescafe32 как решение
Решение
Может комментарии помогут:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
#include #include #include #include // Функция выделяет из строки число. // src - обрабатываемая строка (buf) // dst - строка, куда сохранется число // возвращает положение в src, где закончилась обработка строки char *get_num(char *src, char *dst) { bool is_num = false; // если первый встреченый символ цифра значит слово (лексема) - "число" // необходимо для удаления пробелов перед числом while(1) { if(false == is_num) { // встретили символ if(isdigit(*src)) { // проверяем является ли цифрой is_num = true; // мы лексеме - "число" *dst++ = *src++; // сохраняем первую цифру } else { ++src; // удаляем лишнее перед числом } } else { if(isdigit(*src)) { *dst++ = *src++; // сохраняем цифры в dst } else { break; // встретили символ не цифру или пробел закончили обработку } } } *dst = '\0'; // последний элемент строки '\0' return src; // место где закончили обрабатывать строку // эта функция не обрабатыватет ошибки типа "sa12" "21dawd1" и тд // просто будет выделять цифры из строки типа "sa12"->"12" "21dawd1"->"21" // думаю пока не стоит усложнять код } // Функция построена по томуже принципу, что и get_num(), только ищет оператор. // src - обрабатываемая строка (buf) // op - указатель на символ в котором будет храниться оператор // возвращает положение в src, где закончилась обработка строки char *get_op(char *src, char *op) { while(1) { if(*src == '+') { // если совподение найдено *op = *src++; // сохраняем оператор break; // завершаем обработку } ++src; // удаляем лишнее перед оператором } return src; // место где закончили обрабатывать строку // опять же функция не обрабатывает ошибки ввиде посторонних символов // она их просто игнорирует } int main(int argc, char *argv[]) { int SIZE_BUF = 10; // размер буфера char buf[SIZE_BUF]; // буфер ввода обрабатываемой строки char num1[SIZE_BUF]; // строка с первым числом char num2[SIZE_BUF]; // строка со вторым числом char op; // символ - оператор // . fgets(buf, SIZE_BUF, stdin); // считываем строку в буфер buf // число считанных символов не должно превышать SIZE_BUF // из потока ввода stdin char *tmp = get_num(buf, num1); // из буфера выделяем первое число // tmp указывет на место, где закончилась обработка строки tmp = get_op(tmp, &op); // дале считываем из буфера оператор tmp = get_num(tmp, num2); // теперь последне число fprintf(stdout, "num1 = %s; op = %c; num2 = %s;\n", num1, op, num2); return EXIT_SUCCESS; }
Немного о строках в Си, или несколько вариантов оптимизировать неоптимизируемое
Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики.
Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов).
У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу.
И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку.
char *str = (char *)malloc(sizeof(char) * strlen(buffer));
buffer — стековая переменная, в которую заносились данные с клавиатуры.
Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?».
Поверьте, может.
А что именно — читайте по катом.
Немного теории — своеобразный ЛикБез.
Если знаете — листайте до следующего хэдера.
Строка в C — это массив символов, который по-хорошему всегда должен заканчиваться ‘\0’ — символом конца строки. Строки на стеке (статичные) объявляются вот так:
char str[n] = < 0 >;
n — размер массива символов, то же, что и длина строки.
Присваивание < 0 >— «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор.
Так же на стеке можно сразу проинициализировать строку:
char buf[BUFSIZE] = "default buffer text\n";
Помимо этого строку можно объявить указателем и выделить под нее память на куче (heap):
char *str = malloc(size);
size — количество байт, которые мы выделяем под строку. Такие строки называются динамическими (вследствие того, что нужный размер вычисляется динамически + выделенный размер памяти можно в любой момент увеличить с помощью функции realloc() ).
В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче — я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size — это уже совсем другая история…
Думаю. пока хватит. Идем дальше.
Нам поможет valgrind
В своей предыдущей статье я также упоминал о нем. Valgrind (раз — вики-статья, два — небольшой how-to) — очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста — как раз те вещи, которые чаще всего всплывают при работе со строками.
Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind:
#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() < char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); >
И, собственно, результат работы программы:
[indever@localhost public]$ gcc main.c [indever@localhost public]$ ./a.out -> Hello, Habr!
Пока ничего необычного. А теперь давайте запустим эту программу с valgrind!
[indever@localhost public]$ valgrind --tool=memcheck ./a.out ==3892== Memcheck, a memory error detector ==3892== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==3892== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==3892== Command: ./a.out ==3892== ==3892== Invalid write of size 2 ==3892== at 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004c is 12 bytes inside a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Invalid read of size 1 ==3892== at 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== by 0x4E89AD0: vfprintf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4E90718: printf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004d is 0 bytes after a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Hello, Habr! ==3892== ==3892== HEAP SUMMARY: ==3892== in use at exit: 0 bytes in 0 blocks ==3892== total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated ==3892== ==3892== All heap blocks were freed -- no leaks are possible ==3892== ==3892== For counts of detected and suppressed errors, rerun with: -v ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
==3892== All heap blocks were freed — no leaks are possible — утечек нет, и это радует. Но стоит опустить глаза чуть пониже (хотя, хочу заметить, это лишь итог, основная информация немного в другом месте):
==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
3 ошибки. В 2х контекстах. В такой простой программе. Как!?
Да очень просто. Весь «прикол» в том, что функция strlen не учитывает символ конца строки — ‘\0’. Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!\n\0»), он будет проигнорирован.
Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы.
Собственно, правильная версия программы будет выглядеть так:
#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() < char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); >
Пропускаем через valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==3435== ==3435== HEAP SUMMARY: ==3435== in use at exit: 0 bytes in 0 blocks ==3435== total heap usage: 2 allocs, 2 frees, 1,038 bytes allocated ==3435== ==3435== All heap blocks were freed -- no leaks are possible ==3435== ==3435== For counts of detected and suppressed errors, rerun with: -v ==3435== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Отлично. Ошибок нет, +1 байт выделяемой памяти помог решить проблему.
Что интересно, в большинстве случаев и первая и вторая программа будут работать одинаково, но если память, выделенная под строку, в которую не влез символ окончания, не была занулена, то функция printf(), при выводе такой строки, выведет и весь мусор после этой строки — будет выведено все, пока на пути printf() не встанет символ окончания строки.
Однако, знаете, (strlen(str) + 1) — такое себе решение. Перед нами встают 2 проблемы:
- А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
- Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.
snprintf()
int snprintf(char *str, size_t size, const char *format, . ); — функция — расширение sprintf, которая форматирует строку и записывает ее по указателю, переданному в качестве первого аргумента. От sprintf() она отличается тем, что в str не будет записано байт больше, чем указано в size.
Функция имеет одну интересную особенность — она в любом случае возвращает размер формируемой строки (без учета символа конца строки). Если строка пустая, то возвращается 0.
Одна из описанных мною проблем использования strlen связана с функциями sprintf() и snprintf(). Предположим, что нам надо что-то записать в строку str. Конечная строка содержит значения других переменных. Наша запись должна быть примерно такой:
char * str = /* тут аллоцируем память */; sprintf(str, "Hello, %s\n", "Habr!");
Встает вопрос: как определить, сколько памяти надо выделить под строку str?
char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1));
— не прокатит. Прототип функции strlen() выглядит так:
#include size_t strlen(const char *s);
const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.
Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:
#include #include #include void main() < /* Т.к. snprintf() не учитывает символ конца строки, прибавляем его размер к результату */ size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0'); char *str = malloc(needed_mem); snprintf(str, needed_mem, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); >
Запускаем программу в valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==4132== ==4132== HEAP SUMMARY: ==4132== in use at exit: 0 bytes in 0 blocks ==4132== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==4132== ==4132== All heap blocks were freed -- no leaks are possible ==4132== ==4132== For counts of detected and suppressed errors, rerun with: -v ==4132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) [indever@localhost public]$
Отлично. Поддержка аргументов у нас есть. Благодаря тому, что мы в качестве второго аргумента в функцию snprintf() передаем ноль, запись по нулевому указателю никогда не приведет к Seagfault. Однако, несмотря на это функция все равно вернет необходимый под строку размер.
Но с другой стороны, нам пришлось завести дополнительную переменную, да и конструкция
size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0');
выглядит еще хуже, чем в случае с strlen().
Вообще, + sizeof(‘\0’) можно убрать, если в конце строки формата явно указать ‘\0’ (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!\n\0», «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт).
Надо что-то сделать. Я немного подумал и решил, что сейчас настал час воззвать к мудрости древних. Опишем макрофункцию, которая будет вызывать snprintf() с нулевым указателем в качестве первого аргумента, и нулем, в качестве второго. Да и про конец строки не забудем!
#define strsize(args. ) snprintf(NULL, 0, args) + sizeof('\0')
Да, возможно, для кого-то будет новостью, но макросы в си поддерживают переменное количество аргументов, и троеточие говорит препроцессору о том, что указанному аргументу макрофункции (в нашем случае это args) соответствует несколько реальных аргументов.
Проверим наше решение на практике:
#include #include #include #define strsize(args. ) snprintf(NULL, 0, args) + sizeof('\0') void main() < char *str = malloc(strsize("Hello, %s\n", "Habr!")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); >
Запускаем с valgrund:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6432== ==6432== HEAP SUMMARY: ==6432== in use at exit: 0 bytes in 0 blocks ==6432== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==6432== ==6432== All heap blocks were freed -- no leaks are possible ==6432== ==6432== For counts of detected and suppressed errors, rerun with: -v ==6432== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Да, ошибок нет. Все корректно. И valgrind доволен, и программист наконец может пойти поспать.
Но, напоследок, скажу еще кое-что. В случае, если нам надо выделить память под какую-либо строку (даже с аргументами) есть уже полностью рабочее готовое решение.
Речь идет о функции asprintf:
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, . );
В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.
Наша программа, написанная с использованием asprintf() будет выглядеть так:
#include #include #include void main() < char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); >
И, собственно, в valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6674== ==6674== HEAP SUMMARY: ==6674== in use at exit: 0 bytes in 0 blocks ==6674== total heap usage: 3 allocs, 3 frees, 1,138 bytes allocated ==6674== ==6674== All heap blocks were freed -- no leaks are possible ==6674== ==6674== For counts of detected and suppressed errors, rerun with: -v ==6674== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Все отлично, но, как видите, памяти всего было выделено больше, да и alloc’ов теперь три, а не два. На слабых встраиваемых системах использование это функции нежелательно.
К тому же, если мы напишем в консоли man asprintf, то увидим:
CONFORMING TO These functions are GNU extensions, not in C or POSIX. They are also available under *BSD. The FreeBSD implementation sets strp to NULL on error.
Отсюда ясно, что данная функция доступна только в исходниках GNU.
Заключение
В заключение я хочу сказать, что работа со строками в C — это очень сложная тема, которая имеет ряд нюансов. Например, для написания «безопасного» кода при динамическом выделении памяти рекомендуется все же использовать функцию calloc() вместо malloc() — calloc забивает выделяемую память нулями. Ну или после выделения памяти использовать функцию memset(). Иначе мусор, который изначально лежал на выделяемом участке памяти, может вызвать вопросы при дебаге, а иногда и при работе со строкой.
Больше половины моих знакомых си-программистов (большинство из них — начинающие), решивших по моей просьбе задачу с выделением памяти под строки, сделали это так, что в конечном итоге это привело к ошибкам контекста. В одном случае — даже к утечке памяти (ну, забыл человек сделать free(str), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.
Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил — никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.
Я верю, что после прочтения этой статьи ваш код станет чуточку лучше 🙂
Удачи, Хабр!