【问题标题】:C++ - Eclipse behavior is different while debugging and runningC++ - Eclipse 行为在调试和运行时有所不同
【发布时间】:2019-02-01 19:10:22
【问题描述】:

我正在使用Eclipse IDEC++ 中创建flood fill 算法。该算法包含一个名为image 的向量。根据用户输入在此图像中绘制一个正方形,将图像分割为两个区域(正方形内部和外部)。 clicked point 被作为输入。如果此点在正方形内,则正方形内的所有点都将更改为fill_value(在本例中为 25)。如果在正方形外,正方形外的所有像素都变为fill_value。代码如下:

#include <iostream>
#include <vector>
#include <queue>
#include <cstddef>
#include <stdexcept>

class Point
{
    std::size_t x_cord;
    std::size_t y_cord;
public:
    Point(std::size_t x, std::size_t y):x_cord{x}, y_cord{y}
    {

    }
    std::size_t x() const
    {
        return x_cord;
    }
    std::size_t y() const
    {
        return y_cord;
    }

};

bool check_point(Point pt, std::size_t x_dim, std::size_t y_dim)
{
    if(pt.x() >= 0 && pt.x() < x_dim && pt.y() >= 0 && pt.y() < y_dim)
    {
        return true;
    }
    return false;
}

void get_neighbors(Point& curr_point, std::queue<Point>& q, std::vector<std::vector<int>>& image, int old_val)
{
    std::vector<Point> neighbors;
    std::size_t x_dim = image.size();
    std::size_t y_dim;
    if(x_dim > 0)
    {
        y_dim = image[0].size();
    }
    if(check_point(Point{curr_point.x() - 1, curr_point.y()}, x_dim, y_dim) && image[curr_point.x() - 1][curr_point.y()] == old_val)
    {
        q.push(Point{curr_point.x() - 1, curr_point.y()});
    }
    if(check_point(Point{curr_point.x(), curr_point.y() - 1}, x_dim, y_dim) && image[curr_point.x()][curr_point.y() - 1] == old_val)
    {
        q.push(Point{curr_point.x(), curr_point.y() - 1});
    }
    if(check_point(Point{curr_point.x() + 1, curr_point.y()}, x_dim, y_dim) && image[curr_point.x() + 1][curr_point.y()] == old_val)
    {
        q.push(Point{curr_point.x() + 1, curr_point.y()});
    }
    if(check_point(Point{curr_point.x(), curr_point.y() + 1}, x_dim, y_dim) && image[curr_point.x()][curr_point.y() + 1] == old_val)
    {
        q.push(Point{curr_point.x(), curr_point.y() + 1});
    }
}

void flood_fill(std::vector<std::vector<int>>& image, Point clicked, int new_val)
{
    int old_val = image[clicked.x()][clicked.y()];
    std::queue<Point> q;
    q.push(clicked);
    while(!q.empty())
    {
        Point curr_point = q.front();
        get_neighbors(curr_point, q, image, old_val);
        image[curr_point.x()][curr_point.y()] = new_val;
        q.pop();
    }
}

void draw_square(std::vector<std::vector<int>>& image, Point top_left_corner, int length)
{
    std::size_t x_0 = top_left_corner.x();
    std::size_t y_0 = top_left_corner.y();
    std::size_t x;
    std::size_t y;
    for(x = x_0; x < x_0 + length; x++)
    {
        image[x][y_0] = 1;
        image[x][y_0 + length - 1] = 1;
    }
    for(y = y_0; y < y_0 + length; y++)
    {
        image[x_0][y] = 1;
        image[x_0 + length - 1][y] = 1;
    }
}

void print_image(std::vector<std::vector<int>>& image, std::size_t x_dim, std::size_t y_dim)
{
    for(std::size_t i = 0; i < x_dim; i++)
    {
        for(std::size_t j = 0; j < y_dim; j++)
        {
            std::cout << image[i][j] << "\t";
        }
        std::cout << "\n";
    }
    std::cout << "\n";
}

int main()
{
    try
    {
        std::size_t x_dim, y_dim;
        std::size_t x, y;
        std::size_t c_x = 0;
        std::size_t c_y = 0;
        int length;
        int fill_value = 25;
        std::cout << "Enter the dimensions of the image: \n";
        std::cin >> x_dim >> y_dim;
        std::vector<std::vector<int>> image(x_dim, std::vector<int>(y_dim, 0));
        std::cout << "Enter the top left point coordinates and length for the square: \n";
        std::cin >> x >> y >> length;
        Point top_left_corner{x, y};
        if(!check_point(top_left_corner, x_dim, y_dim) || !check_point(Point{top_left_corner.x() + length - 1, top_left_corner.y() + length - 1}, x_dim, y_dim))
        {
            throw std::out_of_range{"Invalid Access"};
        }
        draw_square(image, top_left_corner, length);
        std::cout << "Before Flood Fill: \n";
        print_image(image, x_dim, y_dim);
        std::cout << "Enter point to be clicked: \n";
        std::cin >> c_x >> c_y;
        Point clicked{c_x, c_y};
        //std::cout << "here1\n";
        if(!check_point(clicked, x_dim, y_dim))
        {
            throw std::out_of_range{"Invalid Access"};
        }
        std::cout << "here2\n";
        flood_fill(image, clicked, fill_value);
        std::cout << "After Flood Fill: \n";
        print_image(image, x_dim, y_dim);
    }
    catch(std::out_of_range& e)
    {
        std::cerr << e.what() << "\n";
    }
    return 0;
}

它适用于某些输入。但是,请考虑以下输入(Before Flood Fill 之后的数组是程序输出,而不是输入):

Enter the dimensions of the image: 
20 20
Enter the top left point coordinates and length for the square: 
15 15 4
Before Flood Fill: 
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   1   1   1   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   0   0   1   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   0   0   1   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   1   1   1   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   

Enter point to be clicked: 
1 2

程序在此之后占用了大量处理,并且不会继续或不会终止。我的想法是这是由于flood_fill 函数的低效实现。当我使用调试器时,std::cout &lt;&lt; "here2\n"; 语句会在控制台上打印here2,而我只是运行程序时不会打印它。所以,我不确定是flood_fill 导致了这个问题还是其他原因。

  1. 为什么运行和调试时行为不同?

  2. 请提供调试建议。

注意:我的想法是,由于值正在更改,它们将自动无法通过作为合格邻居的检查。但我看到我的代码存在问题。到值更改时,可能会多次添加特定的邻居。这两个答案都帮助我确定了这一点。谢谢你们。

【问题讨论】:

  • 调试与以其他方式运行时的不同行为通常表明代码覆盖了它不应该覆盖的内存(调试或未经优化的编译会更改程序使用的内存中数据的布局,因此,不良操作的后果可能会改变)。您正在使用 vector&lt;vector&lt;int&gt;&gt; 将变量用作与向量的实际大小无关的范围内的索引 - 可能您正在运行。除此之外,很难给出具体的建议,因为代码太多。阅读如何创建minimal reproducible example - 以增加获得有用建议的机会。
  • 您对洪水填充算法的实现可能完全是假的。即使使用调试版本,如此小的图像上的洪水填充也应该是即时的。您需要使用调试器自行调试。尝试创建尽可能小的图像以进行调试。
  • 是的,这是假的,因为点范围检查失败,正如我的回答中所解释的那样。 std::size_ts 正在下溢。
  • 仅供参考,y_dim in get_neighbors 不确定地使用,如果你曾经为 image 提供一个空向量向量的函数。在这种情况下,未初始化的y_dim 在重复传递给check_point 时仍然不确定。 Prolly 想为此做点什么。
  • floodfill 最终将通过 x==0 或 y==0 或两者都传递到 get_neighbors 点,这反过来将导致访问索引为 -1 的向量的元素,即 ub

标签: c++ eclipse debugging flood-fill


【解决方案1】:

std::size_t 是无符号整数类型。当它达到负值时,它会回绕到最大值,即正值。因此,您的所有x &gt;= 0y &gt;= 0 始终为真。尝试将您所有的size_t 换成int 之类的东西。

您的另一个问题是您的洪水填充队列已经达到了巨大的规模。这是因为添加点比删除点快。你需要一些方法来判断一个点之前是否被洪水填充过:

void get_neighbors(Point& curr_point, std::queue<Point>& q, std::vector<std::vector<int>>& image, int old_val, std::vector<std::vector<bool>> &visited)
{
  std::vector<Point> neighbors;
  int x_dim = image.size();
  int y_dim;
  if (x_dim > 0)
  {
    y_dim = image[0].size();
  }
  if (check_point(Point{ curr_point.x() - 1, curr_point.y() }, x_dim, y_dim) && image[curr_point.x() - 1][curr_point.y()] == old_val && !visited[curr_point.x() - 1][curr_point.y()])
  {
    q.push(Point{ curr_point.x() - 1, curr_point.y() });
    visited[curr_point.x() - 1][curr_point.y()] = true;
  }
  if (check_point(Point{ curr_point.x(), curr_point.y() - 1 }, x_dim, y_dim) && image[curr_point.x()][curr_point.y() - 1] == old_val && !visited[curr_point.x()][curr_point.y() - 1])
  {
    q.push(Point{ curr_point.x(), curr_point.y() - 1 });
    visited[curr_point.x()][curr_point.y() - 1] = true;
  }
  if (check_point(Point{ curr_point.x() + 1, curr_point.y() }, x_dim, y_dim) && image[curr_point.x() + 1][curr_point.y()] == old_val && !visited[curr_point.x() + 1][curr_point.y()])
  {
    q.push(Point{ curr_point.x() + 1, curr_point.y() });
    visited[curr_point.x() + 1][curr_point.y()] = true;
  }
  if (check_point(Point{ curr_point.x(), curr_point.y() + 1 }, x_dim, y_dim) && image[curr_point.x()][curr_point.y() + 1] == old_val && !visited[curr_point.x()][curr_point.y() + 1])
  {
    q.push(Point{ curr_point.x(), curr_point.y() + 1 });
    visited[curr_point.x()][curr_point.y() - 1] = true;
  }
}

void flood_fill(std::vector<std::vector<int>>& image, Point clicked, int new_val)
{
  if (image.empty()) return;
  int old_val = image[clicked.x()][clicked.y()];
  std::vector<std::vector<bool>> visisted(image.size(), std::vector<bool>(image[0].size(), false));
  std::queue<Point> q;
  q.push(clicked);
  while (!q.empty())
  {
    Point curr_point = q.front();
    get_neighbors(curr_point, q, image, old_val, visisted);
    image[curr_point.x()][curr_point.y()] = new_val;
    q.pop();
  }
}

【讨论】:

  • 反对票是我的。负数组索引会导致未定义的行为,就像“环绕”到非常大的值的索引一样。
  • 不,签名不足/溢出不是未定义的行为,只有签名才是。数组访问只发生在check_point 之后,但由于下溢,该函数对负整数失败。
  • @N00byEdge 将 std::size_t 更改为 int 尚未修复。我正在再次调试整个事情,看看有什么问题。
  • 改变类型后,现在只有你的洪水填充被破坏了。您将事物添加回队列的速度比删除它们的速度要快。您现在只需将每个点标记为已访问。
  • @N00byEdge - 我没有说无符号溢出会给出未定义的行为。我说负数组索引可以。至多,您的建议所做的只是更改真正问题所在的细节。
【解决方案2】:

你的回溯很糟糕。队列充满了数百万个重复点。您应该在每次迭代时标记所有收集的点,以免它们再次添加到队列中:

void get_neighbors(Point& curr_point, std::queue<Point>& q, std::vector<std::vector<int>>& image, int old_val, int new_val)
{
    std::size_t x_dim = image.size();
    std::size_t y_dim;
    if(x_dim > 0)
    {
        y_dim = image[0].size();
    }
    image[curr_point.x()][curr_point.y()] = new_val;
    if(check_point(Point{curr_point.x() - 1, curr_point.y()}, x_dim, y_dim) && image[curr_point.x() - 1][curr_point.y()] == old_val)
    {
        q.push(Point{curr_point.x() - 1, curr_point.y()});
        image[q.back().x()][q.back().y()] = new_val;
    }
    if(check_point(Point{curr_point.x(), curr_point.y() - 1}, x_dim, y_dim) && image[curr_point.x()][curr_point.y() - 1] == old_val)
    {
        q.push(Point{curr_point.x(), curr_point.y() - 1});
        image[q.back().x()][q.back().y()] = new_val;
    }
    if(check_point(Point{curr_point.x() + 1, curr_point.y()}, x_dim, y_dim) && image[curr_point.x() + 1][curr_point.y()] == old_val)
    {
        q.push(Point{curr_point.x() + 1, curr_point.y()});
        image[q.back().x()][q.back().y()] = new_val;
    }
    if(check_point(Point{curr_point.x(), curr_point.y() + 1}, x_dim, y_dim) && image[curr_point.x()][curr_point.y() + 1] == old_val)
    {
        q.push(Point{curr_point.x(), curr_point.y() + 1});
        image[q.back().x()][q.back().y()] = new_val;
    }
}

void flood_fill(std::vector<std::vector<int>>& image, Point clicked, int new_val)
{
    int old_val = image[clicked.x()][clicked.y()];
    if(old_val == new_val)
    {
         return;
    }
    std::queue<Point> q;
    q.push(clicked);
    while(!q.empty())
    {
        Point curr_point = q.front();
        get_neighbors(curr_point, q, image, old_val, new_val);
        q.pop();
    }
}

【讨论】:

  • 我的想法是,由于值正在更改,它们将自动无法通过检查是否是合格的邻居。但我看到我的代码存在问题。当值发生变化时,可能会多次添加特定的邻居。
  • 需要注意的是new_val == old_val时这个是不行的。那案子应该会处理吧。这也需要更改有符号值。
  • @N00byEdge 不需要更改为有符号值。这在 new_val == old_val 时有效。
猜你喜欢
  • 2016-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-04
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 2016-08-06
相关资源
最近更新 更多