Cреда dev-cpp
/*K&R. Упражнение 2.3. Напишите функцию htoi(s), которая преобразует строку шестнадцатеричных цифр (учитывая необязательные элементы 0х или 0Х) в ее целочисленный эквивалент. В число допустимых цифр входят десятичные цифры от 0 до 9, а также буквы a-f и A-F.
*/
код



При MAXLEN 2 массив s[MAXLEN] должен состоять из 3-х элементов: s[0], s[1], s[2].
символом '\0' закрываю строку
При вводе 123, 1234, в элемент s[2] не записывается '\0', а записывается '3', что видно по преобразованию в 291.
скриншот

При MAXLEN 3, 4, 5 и т. д.
программа работает как положено.

Это баг/фича компилятора mingw или какая-то особенность языка C?

@темы: Вопрос, C++

Комментарии
24.03.2011 в 18:12

IDDQD - Команда молодости нашей, команда, без которой мне не жить.
При MAXLEN 2 массив s[MAXLEN] должен состоять из 3-х элементов: s[0], s[1], s[2].
Учим матчасть. При MAXLEN 2 массив s должен состоять из двух элементов. s[0] и s[1]. '\0' как дополнительный элемент массива добавляется только к строковым литералам.
24.03.2011 в 18:26

Пау-чок
Компьютер делает не то, что вам надо, а то, что вы ему говорите =)

При MAXLEN=2 под массив s в памяти будет выделено ровно 2*sizeof(char)=2 байт. Т.е. в него можно будет записать, не выходя за границы, всего два символа. Вы же считаете, что там 3 элемента. И вот что из этого получается...

Память для локальных переменных функций выделяется из стека. Т.е. когда вы объявляете
,
компилятор стеке выделает sizeof(s[2])+iszeof(i)=4 байта, 2 под массив s и два - под переменную i. Расположены они вот так (X - это адрес вершины стека):
АдресЧто хранит
X-3s[0]
X-2s[1]
X-1i, младший байт
X-0i, старший байт


Теперь вот что происходит при выполнении цикла while при вводе строки "1234" (а именно - после строки 17 приведённого вами кода):

АдресДо циклаИтерация 1Ит. 2Ит. 3
X-3s[0]'\0''1''1''1'
X-2s[1]'\0''\0''2''2'
X-1i, младший байт0xFF01'3'=0x33=51
X-0i, старший байт0xFF000
Значение i-101'3'=0x33=51
Значение s"""1""12""123"


То есть на третьей итерации символ '3' записывается на то место, где хранится переменная i! А т.к. значение символа '3' в численном виде равно 51, то оно вам и выводится.

Вы совершили очень типичную ошибку, которая широко известна в западной литературе как Obi-Wan error (ошибка Оби Вана, которая так была названа от созвучия с off-by-one error - ошибка "на один меньше"). Это когда выделяется на один элемент памяти меньше, чем используется. Такая ошибка на самом деле может быть незаметна с самого начала. Но её наличие очень часто может быть использовано для взлома программы или выполнения вредоносного кода.
24.03.2011 в 20:11

спасибо,
тогда почему(другой пример) при i = 1 я не получаю символ 's'?


скрин

в каких книгах на русском описаны типичные ошибки(наподобие моей)?
24.03.2011 в 20:40


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

остаётся вопрос: в каких книгах на русском описаны типичные ошибки(наподобие моей)?
24.03.2011 в 21:23

Пау-чок
goga_50 Вы не получаете при i=1 символ 's' потому, что любые (т.е. вообще любые) массивы в си индексируются с нуля (в отличие от Паскаля и производных от него).
И когда вы объявляете char[4] s= "sym", вы получаете именно то, что закомментировали. Т.е. s[0] будет равен 's', s[1] - 'y', s[2] - 'm', s[3] - '\0'.

Чтобы получить в данном коде желаемый эффект, следует вместо:

написать:


Если же вас смутило то, что строка printf("\n%s", s) выдаёт "sym", то это опять же - вполне ожидаемая ситуация. В си любой массив, по сути, является ничем иным, как указателем на первый элемент массива. И естественно, компилятор воспринимает его никак иначе, чем элемент того типа, который определён как элемент этого массива. Поясню эту фразу строкой из международного стандарта, описывающего диалект языка C99:
===
A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero).
====
Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным значением элемента массива. Определение индексного оператора
[] таково, что E1[E2] полностью эквивалентно выражению (*((E1)+(E2))). Из-за правил приведения, которые распространяются на бинарный оператор +, если E1 является массивом (т.е. указателем на первый элемент массива), а E2 - целым числом, то E1[E2] является значением E2'ого элемента массива E1 (считая с нуля).
===

Иными словами, когда вы пишете arr[i], где массив arr определен как [N] arr, то вы получаете значение, равное *(*)((void*)arr+i*sizeof()).
Понимаю, с непривычки эту запись понять довольно сложно, особенно если слабо знаком с указателями, так что если есть вопросы - задавайте. Можно и личными сообщениями - я всегда рад помочь, если знаю чем =)

По поводу же книжек. Знания о типичных ошибках были мною лично получены изначально из книги "Защищённый код" (авторы: Майкл Ховард, Дэвид Леблан), где основной упор делается на защиту программ и информации, а посему вряд ли может быть полезен на начальных этапах знакомства с си. А затем - из википедии, со страниц ru:Ошибки программирования и en:Programming bugs.
24.03.2011 в 21:24

Пау-чок
goga_50 Вы меня опередили =) Пока писал - вы уже разобрались с вопросом, прошу прощения за нерасторопность.

P.s. В любом случае, если есть вопросы по си - обращайтесь без стеснения =)
24.03.2011 в 21:31

ок