Поправил ошибку в bc

Вчера часа проснулся около часа ночи, сна ни в одном глазу. Ворочался-ворочался, думаю надо бы заняться чем-нибудь, мозг утомить. Решил почитать исходный код bc. Он остался у меня с того раза, когда я коммитил туда своё предложение по ускорению функции band().

Бродил по коду туда-сюда, читал случайные куски, пока не дошёл до кусочка, проверяющего дублирующиеся параметры у функции. Вот это место:

for (i = 0; i < f->autos.len; ++i)
{
	// Get the auto.
	BcAuto* aptr = bc_vec_item(&f->autos, i);

	// If they match, barf.
	if (BC_ERR(idx == aptr->idx && type == aptr->type))
	{
		const char* array = type == BC_TYPE_ARRAY ? "[]" : "";

		bc_error(BC_ERR_PARSE_DUP_LOCAL, line, name, array);
	}
}

Посмотрел что тут происходит и заметил, что в перечислении, описывающем виды параметров, вообще-то три значения — скаляр, массив и массив, передающийся по ссылке. При этом код выше написан так, как будто их только два. Небольшой тест показал, что так и есть. Следующий код должен порождать ошибку, между тем, он выполняется как ни в чём не бывало:

define a(*t[], t[]) {}

Подумал, что уже достаточно утомился, чтобы лечь спать, но не тут-то было! Стоило закрыть глаза, как мозг начал продумывать как поизящнее исправить этот баг. Пришлось снова открыть ноутбук и править.

Уже утром я прочитал сообщение по моему коммиту — автор bc попросил отформатировать код и написать небольшой тест. Я всё исправил и теперь ещё немного моего кода есть в этой прекрасной утилите!

6 комментариев

Странности bc

Чем больше узнаёшь bc, тем больше в нём находишь странностей. Особенно меня поражают массивы.

Например, как вы думаете, чему равна длина массива, в который мы ещё ни разу не инициализировали? Правильно, единице. Почему? Да чёрт его знает, так в коде написано:

void
bc_array_init(BcVec* a, bool nums)
{
    BC_SIG_ASSERT_LOCKED;

    // Set the proper vector.
    if (nums) bc_vec_init(a, sizeof(BcNum), BC_DTOR_NUM);
    else bc_vec_init(a, sizeof(BcVec), BC_DTOR_VEC);

    // We always want at least one item in the array.
    bc_array_expand(a, 1);
}

Или вот такой код:

define void f(*a[]) {
    . = a[99]
}

f(b[]); length(b[])

Чему будет равна длина массива b[]?

Правильный ответ — она будет равна ста. Потому что в функции, куда мы передали этот массив по ссылке, происходит чтение из 99-го (массивы начинаются с нуля) элемента. В коде bc ясно сказано:

// The bc spec says that if an element is accessed that does not
// exist, it should be preinitialized to 0. Well, if we access
// an element *way* out there, we have to preinitialize all
// elements between the current last element and the actual
// accessed element.
if (v->len <= idx)
{
    BC_SIG_LOCK;
    bc_array_expand(v, bc_vm_growSize(idx, 1));
    BC_SIG_UNLOCK;
}

Как только идёт обращение к несуществующему элементу массива, неважно, для чтения или записи, он получается значение «ноль».

В этих случаях я даже уже и не понимаю — баги это или нет. Вроде нет, но выглядит контринтуитивно.

12 комментариев