Возможная ошибка памяти в коде и возможное решение?
Приведенный ниже исходный код является урезанной версией некоторого более подробного исходного кода C, который анализирует входные строки, чтобы определить, соответствуют ли они предопределенным шаблонам. Код пытается проанализировать входную строку (которую вы можете считать допустимой строкой с нулевым символом в конце). Если строка содержит действительное целое число без знака, функция возвращает 0, в противном случае она возвращает ошибку -1. Целое число без знака соответствует регулярному выражению ˆ[0-9]+$.
Я попытался запустить команду valgrind, чтобы выяснить возможную ошибку, которая отображала следующий вывод (который я не могу понять).
==15269==
==15269== Invalid read of size 1
==15269== at 0x400770: parse_exact (assign2b.c:23)
==15269== by 0x400957: xtz_parse_unsigned (assign2b.c:82)
==15269== by 0x400A26: test_parse_unsigned (assign2b.c:102)
==15269== by 0x400B06: main (assign2b.c:128)
==15269== Address 0x51f2045 is 0 bytes after a block of size 5 alloc'd
==15269== at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==15269== by 0x4EBAD81: strdup (strdup.c:43)
==15269== by 0x400AF1: main (assign2b.c:127)
==15269==
==15269== Invalid read of size 1
==15269== at 0x400770: parse_exact (assign2b.c:23)
==15269== by 0x400957: xtz_parse_unsigned (assign2b.c:82)
==15269== by 0x400A26: test_parse_unsigned (assign2b.c:102)
==15269== by 0x400B9B: main (assign2b.c:142)
==15269== Address 0x51f2135 is 0 bytes after a block of size 5 alloc'd
==15269== at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==15269== by 0x400B72: main (assign2b.c:140)
Ниже приведен код. Пожалуйста, сообщите об ошибках в коде и возможных решениях, а также о том, как их можно вывести с помощью valgrind.
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define OK 9999
#define EOS '\0'
#define XT_SUCCESS 0
#define XT_FAIL -1
typedef int (*PARSE_FUNC)(const char *s, const char **endptr);
static int parse_exact(const char *s, const char **endptr, PARSE_FUNC pfunc)
{
const char *cp = s;
int c;
int state = 1;
while (state != XT_SUCCESS && state != XT_FAIL)
{
c = *cp++; // nextchar
switch(state)
{
case 1:
state = pfunc(--cp, endptr);
cp = *endptr;
if (state == XT_SUCCESS) state = 2;
else cp++; // on FAIL jump ahead to get undone on exit
break;
case 2:
if (EOS == c) state = OK;
else state = XT_FAIL;
break;
case OK:
state = XT_SUCCESS;
break;
default:
/* LOGIC ERROR */
assert(0==1);
break;
}
}
if (endptr)
*endptr = --cp;
return state;
}
static int base_unsigned(const char *s, const char **endptr)
{
const char *cp = s;
int c;
int state = 1;
while (state != XT_SUCCESS && state != XT_FAIL)
{
c = *cp++; // getnextchar
switch(state)
{
case 1:
if (isdigit(c)) state = 2;
else state = XT_FAIL;
break;
case 2:
if (isdigit(c)) state = 2;
else state = XT_SUCCESS;
break;
default:
/* LOGIC ERROR */
assert(0==1);
break;
}
}
if (endptr)
*endptr = --cp;
return state;
}
int xtz_parse_unsigned(const char *s, const char **endptr)
{
PARSE_FUNC pfunc = base_unsigned;
return parse_exact(s, endptr, pfunc);
}
void xt_pr_error(int status, const char *s, const char *endptr)
{
if (0 != status)
{
if (endptr[0])
printf("ERROR: '%c' at position %d is not allowed", *endptr, (endptr - s)+1);
else if ((endptr - s) > 0)
printf("ERROR: cannot end with '%c'", endptr[-1]);
else
printf("ERROR: value is empty");
}
}
void test_parse_unsigned(const char *s, int expected)
{
int status;
const char *endptr; // Ptr to first invalid character
status = xtz_parse_unsigned(s, &endptr);
printf("Test input='%s' status=%d ", s, status);
xt_pr_error(status, s, endptr);
if (status != expected)
printf(" NOT EXPECTED!\n");
else
printf(" (OK)\n");
}
int main(void)
{
char s1234[] = "1234";
char s12a4[] = "12a4";
char *ptr;
// Tests with string literals
test_parse_unsigned("1234", XT_SUCCESS);
test_parse_unsigned("12a4", XT_FAIL);
// Tests with static strings arrays
test_parse_unsigned(s1234, XT_SUCCESS);
test_parse_unsigned(s12a4, XT_FAIL);
// Tests using strdup()
ptr = strdup("1234");
test_parse_unsigned(ptr, XT_SUCCESS);
free(ptr);
ptr = strdup("123a");
test_parse_unsigned(ptr, XT_FAIL);
free(ptr);
ptr = strdup("1a34");
test_parse_unsigned(ptr, XT_FAIL);
free(ptr);
// Test using malloc and strcpy()
ptr = malloc(5);
strcpy(ptr, "1234");
test_parse_unsigned(ptr, XT_SUCCESS);
free(ptr);
return 0;
}
2 ответа
По вашему коду трудно сказать, где настоящая ошибка, вам придется пройти через нее с помощью отладчика. Но, судя по всему, прочитайте не более 1 байта, ваши строки не завершаются должным образом нулем или вы плохо справляетесь с этим условием.
Функция, на которую указывает valgrind, немного трудна для понимания, потому что у вас нет явного условия для конца строки, то есть когда c
является '\0'
,
Также:
Вещи как *cp++
принадлежать музею, не использовать выражения для их побочных эффектов. Здесь вместо while
цикл вы могли бы легко иметь for
цикл с cp
как переменная итерации
for (const char *cp = s;
state != XT_SUCCESS && state != XT_FAIL;
++cp) {
...
}
Ваша смесь использования вашей переменной состояния с именованными константами и числами является безумной и нечитаемой для других или для вас самих, если вы вернетесь через неделю
В функции parse_exact
Вы читаете одну позицию за пределами EOS. Подробно это то, что происходит, когда вы достигаете конца входной строки:
c = *cp++; // nextchar
читает символ NUL (EOS
).
if (EOS == c) state = OK;
Потому что государство ни XT_SUCCESS
ни XT_FAIL
, другой проход сделан через цикл.
c = *cp++; // nextchar
читает символ за EOS
, В некоторых системах это принято, но при строгой проверке границ это не так. В вашем случае возникает ошибка.
case OK:
state = XT_SUCCESS;
В конце концов, state
стал бы XT_SUCCESS
, Меня удивляет, почему у вас это промежуточное состояние OK
,
Я предлагаю вам отказаться OK
и заменить его на XT_SUCCESS
в этой строке кода:
if (EOS == c) state = OK;