2010-05-06 4 views
12

Рассмотрим следующий код.Разница в поведении (GCC и Visual C++)

#include <stdio.h> 
#include <vector> 
#include <iostream> 

struct XYZ { int X,Y,Z; }; 
std::vector<XYZ> A; 

int rec(int idx) 
{ 

    int i = A.size(); 
    A.push_back(XYZ()); 
    if (idx >= 5) 
    return i; 

    A[i].X = rec(idx+1); 

    return i; 
} 

int main(){ 
    A.clear(); 
    rec(0); 
    puts("FINISH!"); 

} 

Я не мог понять причину, почему код дает ошибку сегментации на Linux (IDE используется: Code::Blocks), тогда как на Windows (IDE используется: Visual C++) он не.

Когда я использовал Valgrind, чтобы проверить, что на самом деле проблема, я получил this output.

Я получил Invalid write of size 4 в четырех разных местах. Тогда почему не произошел сбой кода, когда я использовал Visual C++?

Я что-то упустил?

+0

Вам не нужно выделять достаточное пространство в векторе, прежде чем использовать его?Это чистая авария или, возможно, просто другая, более удобная, но менее стандартная реализация 'vector <>'. –

+0

Какая IDE, которую вы используете, не очень полезная информация. Какие версии соответствующих компиляторов вы используете, более уместно. –

+0

@Dennis: Я использовал gcc 4.3 и MSVC++ 2008 –

ответ

17

Рекурсивный вызов rec() может изменять вектор, когда вы назначаете ему значение.

Что произойдет, если вы замените

A[i].X = rec(idx+1); 

с

int tmp = rec(idx+1); 
A[i].X = tmp; 

?

Кроме того, только суммировать полезные комментарии: порядок оценки операнда = операции не определен и поскольку вектор не выделяется заранее, несколько изменения размера может произойти во время рекурсивного вызова rec(), таким образом, недействительности любого итератора до значений в вектор.

+4

Чтобы быть более точным, внутри рекурсивного вызова вызов 'push_back' может привести к недействительности предыдущих указателей. –

+2

Не может ли это быть проблемой i = ++ i; '(за исключением того, что модификация A с правой стороны скрыта в рекурсивном вызове)? Компилятор может свободно оценивать 'A [i]' до или после 'rec' и работает ли он, зависит от того, какой порядок он выбирает для этого. – UncleBens

+2

@ereOn: Да! Я думаю, что ваш ответ правильный. Порядок оценки операндов, связанных с оператором «=», не указан. –

0

Вы используете int i = A.size()

И тогда вы индексацию-структуру в виде массива, но используя значение размера. Вам нужно уменьшить его на 1, например. A[i-1].X = rec(idx+1);

Ah my error - Я не принимал во внимание вектор push_back.

+2

Но он делает индексацию на основе результата 'size()' после * добавления элемента в 'vector'. В этот момент индекс в порядке. –

1

Я получаю «* ошибка для объекта 0x300180: некорректная контрольная сумма для освобожденного объекта - объект, вероятно, был изменен после освобождения. *« когда я запускаю этот код.

Насколько я помню, A[i].X = rec(idx+1) имеет три точки последовательности. Когда оператор [] вызывается на A, когда rec вызывается и в конце. Но порядок первых двух не определен. Итак, если g ++ сначала вычисляет A[i], а затем вызывает rec(idx+1), тогда, когда rec возвращает ссылку, возвращаемую A[i], может быть признана недействительной перераспределением внутренней памяти вектора. В VC++ он может сначала оценивать rec(idx+1), поэтому все вызовы push_back выполняются спереди, что означает, что вызовы A[i] относятся к правильному блоку памяти. В качестве альтернативы, он может делать то же самое, и вы просто не выполняете segfault ... это одна из проблем неопределенного поведения.

Изменение std::vector<XYZ> A; до std::vector<XYZ> A(10); зарезервирует достаточно места на 10 элементов. Это предотвращает вашу конкретную реализацию rec от необходимости перераспределения и исправляет ошибку на моем конце.

+1

+1 для точного объяснения :) –

+0

-1. Это решение, безусловно, будет работать до момента, когда вы увеличите максимальное рекурсивное значение до 11. – deworde

+0

@deworde: Очевидно, что квалификация, которая мешает его конкретной реализации перераспределить. –