14

I've been working on my program, and I decided to turn on some optimizations using g++ -O3. Suddenly, my program started segfaulting. I've hunted the problematic code down, and minimized my program to something that still segfaults (only when using level 3 optimizations). I was hoping someone could take a quick peek at the code (I tried minimizing it as much as possible):

// src/main.cpp
#include "rt/lights/point.hpp"
int main(int argc, char **argv)
{
    rt::Light *light = new rt::light::Point(alg::vector(.0f, 5.0f, 5.0f), rt::Color(1.0f), .5f);
    return 0;
}


// include/rt/lights/point.hpp
#ifndef RT_LIGHT_POINT_HPP_
#define RT_LIGHT_POINT_HPP_

#include "rt/accelerator.hpp"
#include "rt/color.hpp"
#include "rt/intersection.hpp"
#include "rt/light.hpp" // abstract

namespace rt {
namespace light {

class Point : public Light
{
  public:
    Point(alg::vector pos, Color color, float intensity) : Light(intensity * color), pos(pos) {}

    Color get_contrib(const Intersection&, const Accelerator&, const alg::vector& toViewer) const;

  private:
    alg::vector pos;
};

} // namespace light
} // namespace rt

#endif


// include/rt/light.hpp
#ifndef RT_LIGHT_HPP_
#define RT_LIGHT_HPP_

#include "algebra/vector.hpp"
#include "rt/color.hpp"

namespace rt {

class Intersection;
class Accelerator;

class Light
{
  public:
    Light(Color intensity) : intensity(intensity) {}

    virtual Color get_contrib(const Intersection&, const Accelerator&, const alg::vector& toViewer) const = 0;

    Color get_intensity() const {return intensity;}

  protected:
    Color intensity;
};

} // namespace rt

#endif

I would love some insight on why this code only segfaults when using optimizations, and how to stop it from doing so. Thanks!

$ find src/ -name "*.cpp" | xargs g++ -I include/ -O3
$ ./a.out
Segmentation fault

Edit: By request, the constructors for alg::vector

struct vector
{
    float x, y, z;
    vector() : x(.0f), y(.0f), z(.0f) {}
    explicit vector(float f) : x(f), y(f), z(f) {}
    vector(float x, float y, float z) : x(x), y(y), z(z) {}
    // ...

Edit2: Adding gdb output when compiling with -g

(gdb) file a.out
Reading symbols from /home/rob/devel/gbug/a.out...done.
(gdb) run
Starting program: /home/rob/devel/gbug/a.out 

Program received signal SIGSEGV, Segmentation fault.
rt::light::Point::Point (this=0x804b008, pos=..., color=..., intensity=0.5)
    at src/rt/lights/point.cpp:13
13  Point::Point(alg::vector pos, Color color, float intensity) : Light(intensity * color), pos(pos)
(gdb) bt
#0  rt::light::Point::Point (this=0x804b008, pos=..., color=..., intensity=0.5)
    at src/rt/lights/point.cpp:13
#1  0x08048898 in main (argc=1, argv=0xbffff3e4) at src/main.cpp:5

Edit3: Sources for rt::Color.

// include/rt/color.hpp
#ifndef RT_COLOR_HPP_
#define RT_COLOR_HPP_

#include "algebra/vector.hpp"

namespace rt {

/*******************************************************************************
 * CLASS DEFINITION
 */

struct Color
{
    float r, g, b;
    Color() : r(.0f), g(.0f), b(.0f) {}
    explicit Color(float f) : r(f), g(f), b(f) {}
    Color(float r, float g, float b) : r(r), g(g), b(b) {}

    Color& operator+= (const Color&);
    Color& operator*= (const Color&);
    Color& operator*= (float);
};

/*******************************************************************************
 * MEMBER OPERATORS
 */

inline Color& Color::operator+= (const Color& other)
{
    r += other.r;
    g += other.g;
    b += other.b;
    return *this;
}

inline Color& Color::operator*= (const Color& other)
{
    r *= other.r;
    g *= other.g;
    b *= other.b;
    return *this;
}

inline Color& Color::operator*= (float f)
{
    r *= f;
    g *= f;
    b *= f;
}

/*******************************************************************************
 * ADDITIONAL OPERATORS
 */

inline Color operator+ (Color lhs, const Color& rhs)
{
    return lhs += rhs;
}

inline Color operator* (Color lhs, const Color& rhs)
{
    return lhs *= rhs;
}

inline Color operator* (Color c, float f)
{
    return c *= f;
}

inline Color operator* (float f, Color c)
{
    return c *= f;
}

} // namespace rt

#endif
robrene
  • 359
  • 3
  • 14
  • 1
    Have you tried compiling with -g as well to see if the backtrace is at all usable? – Mark Loeser Jan 26 '11 at 15:22
  • I'm curious why you don't compile with `g++ src/*.cpp -Iinclude -O3`. With the `xargs` approach, the compiler would build each file into `a.out`, so I'm assuming they all have `main()`. – chrisaycock Jan 26 '11 at 15:23
  • 1
    can you post the copy constructor of `alg::vector`? – Naveen Jan 26 '11 at 15:24
  • @Mark: I haven't tried that. I must admit I've never attempted to use backtraces before. A short debugging course at university has scarred me pretty bad, it seems incredibly complex for the little program I'm writing... – robrene Jan 26 '11 at 15:30
  • @chrisaycock: NOt every cpp file has a `main()`, I'm mainly using this because it just works. Would your command also go into sub directories inside `src/`? – robrene Jan 26 '11 at 15:32
  • 2
    How does the `operator*()` look like that is used to multiply a `Color` with an intensity? – sth Jan 26 '11 at 15:34
  • @robrene `g++` doesn't have a recursive compile option, like a `-R`. So no. My query is that you aren't compiling with `-c`, so each C++ file should be built into an `a.out` (overwriting each other). I'm surprised you aren't getting linking errors. – chrisaycock Jan 26 '11 at 15:36
  • @sth: I second `operator *` - Despite there not being much code here, I'd wager the problem is there. – Thanatos Jan 26 '11 at 15:58
  • 1
    I'd also compile the code with `-Wall -Wextra`. If you get warnings, they might contain clues to the problem. – Steve Jessop Jan 26 '11 at 15:59
  • 1
    @chrisaycock, GCC can compile multiple sources into one a.out, so it's not the problem here. Requesting sources for Color, too. – Sergei Tachenov Jan 26 '11 at 16:02
  • 1
    @Steve: You solved my problem!!! Turning on the warnings revealed I had stupidly forgotten a return statement in `Color::operator*=(float)` My, do I feel silly now... I'll always be compiling with more warnings on from now on. Thanks! – robrene Jan 26 '11 at 16:25
  • Does the code compiler with zero warnings? Have you turned on all the warnings? Warnings are logical errors (IMO) and need to be removed from the code. – Martin York Jan 26 '11 at 17:41
  • @Martin: albeit sometimes they're errors in the compiler, rather than errors in the user code. – Steve Jessop Jan 26 '11 at 18:10
  • @Steve Jessop: I hate the term "errors in the compiler". It implies non conformance. I would rather say there are some logical errors in the design of the language. In addition to users errors. – Martin York Jan 26 '11 at 19:46
  • @Martin: sometimes my compiler warns me for code which has nothing wrong with it and does exactly what I intended according to the standard. There's no error in the code, so if you're saying there's an error somewhere, then the error was that the compiler issued the warning. I agree with you the warning needs to be removed, which I can do either by changing my (perfectly good) code, or switching off the warning. But I don't think it needs to be removed because it's necessarily a logical error, I do it so I get a clean compile to see the real warnings in future. – Steve Jessop Jan 26 '11 at 22:29
  • @Steve Jessop: No argument there are a few warnings that are superfluous. But as a general rule (and all rules have exceptions) a warning is telling you that your code has some flaw. Now if we want to go into specifics I am sure we can find exceptions. But the changes required are never anything grand (initialize the variable/ careful when down casting etc) and I have yet to come across a situations that harms readability (or maintainability). – Martin York Jan 27 '11 at 04:54

5 Answers5

8

When calculating intensity * color, indirectly this operator is called:

inline Color& Color::operator*= (float f)
{
    r *= f;
    g *= f;
    b *= f;
}

It claims to return a reference to a Color, but doesn't. It should return a reference to *this, like the other operators do:

return *this;
sth
  • 222,467
  • 53
  • 283
  • 367
  • +1. How is this not a compiler error? Is the lack of return value a warning in gcc? – James Jan 26 '11 at 16:56
  • 3
    @James: It's a warning that gets enabled if you specify `-Wall`. – sth Jan 26 '11 at 17:01
  • 4
    Ah. I guess there is a lesson to be learned here... always use -Wall. – James Jan 26 '11 at 17:11
  • 2
    @James: and also use `-Werror`, so that you do not get lazy and leave the warnings "for later". – Matthieu M. Jan 26 '11 at 17:38
  • @James: I would go further. Turn on all warnings. Then make warnings errors so that the code does not generate an executable if there are any warnings. Warnings are usually logical errors in your code (even if they are syntactically valid). Most professional shops have a rule that code should compile with 0 warnings. – Martin York Jan 26 '11 at 17:39
  • @LokiAstari: Impossible to use that policy if you also like to compile with -Wextra which I do. Unfortunately -Wextra reports on some bits of code which, after review, are not a problem so those warnings need to be ignored. – Zan Lynx Dec 14 '12 at 22:34
  • @ZanLynx: There are a couple of ways around this: 1) You can restructure the code so you don't get warnings (this is usually relatively minor (like adding a return)) or 2) You can manually disable warnings you don't want -Wno. Either way there is no excuse for having warnings. I run with `-Wall -Wextra -pedantic -ansi -Werror` as my base line for fun stuff. I turn on a whole bunch more when doing anything serious. The only stuff that is a pain is the Myers warnings `-Weffc++` (as they are often a bit generalized (talking about the warnings not the actual advice)). – Martin York Dec 14 '12 at 22:46
  • Forgot: 3) You can also use pragmas to disable warnings only for specific lines of code. – Martin York Dec 14 '12 at 22:50
  • @MartinYork I suspect Zan wants `-Wextra` as warnings, while `-Wall` as `-Werror`. Ie, "hard" warnings showing up as errors, while "advice" warnings showing up as just warnings. Having warnings in a real build sucks (they turn into a wall of noise), so `-Werror` makes sense, but it would be really nice to be able to have "soft warnings" that say "take a look here again", but only on code you are currently writing or something like that. Because `-Werror` you cannot turn on "soft" advice warnings without wasting time fixing them/disabling them/etc. – Yakk - Adam Nevraumont Jul 05 '21 at 14:08
  • 1
    @Yakk-AdamNevraumont: It may be hard to retroactively add all warnings to an existing project (but it is always worth it). But any project you start from scratch make all warnings an error and it will prevent a lot of issues. Note: Though you may think that `-Wextra` is optional if you read through them they are usually `logical` errors in your thinking and actually point out real problems that need to be fixed. If you don't then a corner case is going to blow up your code at some point in the future. Be responsible in all new projects turn on all the warnings treat all warnings as errors. – Martin York Jul 05 '21 at 16:36
  • @martun Can a computer looking at code ever come up with a warning that isn't always something that needs to be fixed? If so, then warnings as errors prevents you from having that warning, so reduces the warnings you want to activate. This holds for whatever set of warnings you want as errors; so long as there exists even one automated warning of use that isn't worth working around or maually toggling. The result is turning off possibly useful warnings. "Warning: variable name close to a misspelling of English word" could be useful when writing new code, but not after you commit it. – Yakk - Adam Nevraumont Jul 05 '21 at 17:21
3

Time to learn how to debug with gdb!

Recompile all the source code with -g:

find src/ -name "*.cpp" | xargs g++ -I include/ -O3 -g

Then run gdb, load your file, and execute it:

gdb
file a.out
run

gdb will bring you to a command prompt when your program hits the segfault. At the (gdb) prompt type "bt" and hit enter. It will give you the stack trace. The first frame of the stack will be the line of code that caused the segmentation fault.

From there if it's obvious fix it otherwise add the output to your question. Sometimes gdb isn't great at debugging code that's in constructors but try it first and see what it says.

gravitron
  • 3,514
  • 1
  • 20
  • 18
  • I added the gdb output to the original post. Thanks for giving me the quick guide on debugging! I'm not quite sure how this output helps me, since I already knew where the segfault was happening. I just don't know why... – robrene Jan 26 '11 at 15:53
  • Except that the segmentation fault might happen much later after the error code. (undefined behavior is undefined.) – user202729 Dec 13 '21 at 06:28
2

You must realize that it's not the optimization that's breaking your code, the code is already broken. I can't quite see what is going on just by looking at those chunks but seeing as though you are segfaulting on a new statement, I would try and direct my efforts toward checking the input parameters of your new functions. Amongst the many things that happens during an 03 optimization is that the compiler will try to inline function calls, unroll your loops, and create new variables as well as get rid of ones in order to accelerate your execution. As a first step, double check anywhere you have loops and make sure if you're doing something like i < strlen(str) that the statement is not crazy and assert that the input parameters that should not be NULL are in fact not NULL.

Hope this helps.

themaestro
  • 13,750
  • 20
  • 56
  • 75
1

I'm not sure if this is the issue you are seeing, but if you have a class with virtual functions that is used polymorphically, it should have a virtual destructor:

class Light {
   ...
   virtual ~Light {}
   ...
};

Currently, if you would delete the light variable in your main, the ~Light destructor would be called (since the variable has type Light*), instead of the correct ~Point one. Making the destructor virtual fixes this.

sth
  • 222,467
  • 53
  • 283
  • 367
  • I added the virtual destructor, to no avail... The segfault happens before the object is destroyed again, it happens on the line with `new rt::light::Point`. Anything past that doesn't even get executed anymore. – robrene Jan 26 '11 at 15:35
  • @robrene: I suspected as much, since the object *is* never actually destroyed in the sample code... But it should be a step in the right direction anyway. – sth Jan 26 '11 at 15:39
0

There might be some confusion using the member variable pos and the passed in variable pos, try naming them different variable names.

Point(alg::vector pos, Color color, float intensity) : Light(intensity * color), pos(pos) {}

Dan
  • 354
  • 1
  • 4
  • 2
    There shouldn't be any confusion. The argument hides the member variable. It's pretty common to have things like `pos(pos)` in an initialization list. – Sergei Tachenov Jan 26 '11 at 16:13