Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to use #ifdef guards on C++ class member functions?

Suppose you have the following definition of a C++ class:

class A {
// Methods
#ifdef X
// Hidden methods in some translation units
#endif
};

Is this a violation of One Definition Rule for the class? What are the associated hazards? I suspect if member function pointers or virtual functions are used this will most likely break. Otherwise is it safe to use?

I am considering it in the context of Objective C++. The header file is included in both pure C++ and Objective C++ translation units. My idea is to guard methods with Objective-C types with OBJC macro. Otherwise, I have to use void pointer for all Objective-C types in the header, but this way I am losing strong typing and also ugly static casts must be added all over the code.

like image 471
user1668604 Avatar asked Feb 04 '21 06:02

user1668604


People also ask

Is it safe to use Internet?

There's almost no limit to what you can do online. The Internet makes it possible to access information quickly, communicate around the world, and much more. Unfortunately, the Internet is also home to certain risks, such as malware, spam, and phishing.

Why is staying safe online Important?

Most people store a lot of personal information on their computers. If you don't protect your computer properly when you're online, it's possible that personal details could be stolen or deleted without your knowledge. Your computer can be attacked in a number of ways over the internet.

Is it safe to use product keys found online?

Is using product keys found online safe or could someone hack you via a product key or sue you for using it? You won’t be hacked for activating a software license from any product key found online, but some keys have so many times they can be used before they are marked as invalid / become unusable.

Is vanced safe to use?

The safety of Vanced is more complicated than it might seem. The app has an established history of safety and success, and many users have had a great experience with it. Despite that, Vanced is no longer universally safe, and installing it is a much riskier prospect than it used to be.

How safe is WhatsApp?

How Safe Is WhatsApp? 1 Privacy Settings: You can control by setting your profile photo, last seen, and about to be seen by everyone, contacts only, or no one. 2 Two-Step Verification: Create a six-digit pin to enable extra security for your WhatsApp account. 3 Block Unwanted Users: Stop some from contacting you directly from a chat. More items...

Is it safe to use a debit card online?

Compared to a credit card, using a debit card online is probably less safe for several reasons. But it is the top payment priority for people who do not like credit. In this article, we will suggest some tips to secure yourself when using a debit card online. “Is it safe to use a debit card online?” is a question that most users care about.


3 Answers

Yes, it MAY allow a hazard of ODR breach if separate compilation units are allowed to have different state of macro definition X. X should be defined (or not defined) globally across program (and shared objects) before every inclusion of that class definition for program to meet requirement to be compliant. As far as C++ compiler (not preprocessor) concerned, those are two different, incompatible, unrelated class types.

Imagine situation where in compilation unit A.cpp X was defined before class A and in unit B.cpp X was not defined. You would not get any compiler errors if nothing within B.cpp uses those members which were "removed". Both units may be considered well-formed on their own. Now if B.cpp would contain a new expression, it would create an object of incompatible type, smaller than one defined in A.cpp. But any method from class A , including constructor, may cause an UB by accessing memory outside of object's storage when called with object created in B.cpp, because they use the larger definition.

There is a variation of this folly, an inclusion of header file's copy into two or more different folders of build tree with same name of file and POD struct type, one of those folders accessible by #include <filename>. Units with #include "filename" designed to use alternatives. But they wouldn't. Because order of header file lookup in this case is platform-defined, programmer isn't entirely in control of which header would be included in which unit on every platform with #include "filename". As soon as one definition would be changed, even just by re-ordering members, ODR was breached.

To be particularly safe, such things should be done only in compiler domain by using templates, PIMPL, etc. For inter-language communication some middle ground should be arranged, using wrappers or adapters, C++ and ObjectiveC++ may have incompatible memory layout of non-POD objects.

like image 74
Swift - Friday Pie Avatar answered Oct 17 '22 17:10

Swift - Friday Pie


This blows up horribly. Do not do this. Example with gcc:

Header file:

// a.h

class Foo
{
public:
    Foo() { ; }

#ifdef A
    virtual void IsCalled();
#endif
    virtual void NotCalled();
};

First C++ File:

// a1.cpp

#include <iostream>

#include "a.h"

void Foo::NotCalled()
{
    std::cout << "This function is never called" << std::endl;
}

extern Foo* getFoo();
extern void IsCalled(Foo *f);

int main()
{
   Foo* f = getFoo();
   IsCalled(f);
}

Second C++ file:

// a2.cpp

#define A
#include "a.h"
#include <iostream>

void Foo::IsCalled(void)
{
    std::cout << "We call this function, but ...?!" << std::endl;
}

void IsCalled(Foo *f)
{
    f->IsCalled();
}

Foo* getFoo()
{
    return new Foo();
}

Result:

This function is never called

Oops! The code called virtual function IsCalled and we dispatched to NotCalled because the two translation units disagreed on which entry was where in the class virtual function table.

What went wrong here? We violated ODR. So now two translations units disagree on what is supposed to be where in the virtual function table. So if we create a class in one translation unit and call a virtual function in it from another translation unit, we may call the wrong virtual function. Oopsie whoopsie!

Please do not deliberately do thigs that the relevant standards say are not allowed and will not work. You will never be able to think of every possible way it can go wrong. This kind of reasoning has caused many disasters over my decades of programming and I really wish people would stop deliberately and intentionally creating potential disasters.

like image 15
David Schwartz Avatar answered Oct 17 '22 16:10

David Schwartz


Is it safe to use #ifdef guards on C++ class member functions?

In practice (look at the generated assembler code using GCC as g++ -O2 -fverbose-asm -S) what you propose to do is safe. In theory it should not be.

However, there is another practical approach (used in Qt and FLTK). Use some naming conventions in your "hidden" methods (e.g. document that all of them should have dontuse in their name like int dontuseme(void)), and write your GCC plugin to warn against them at compile time. Or just use some clever grep(1) in your build process (e.g. in your Makefile)

Alternatively, your GCC plugin may implement new #pragma-s or function attributes, and could warn against misuse of such functions.

Of course, you can also use (cleverly) private: and most importantly, generate C++ code (with a generator like SWIG) in your build procedure.

So practically speaking, your #ifdef guards may be useless. And I am not sure they make the C++ code more readable.

If performance matters (with GCC), use the -flto -O2 flags at both compile and link time.

See also GNU autoconf -which uses similar preprocessor based approaches.

Or use some other preprocessor or C++ code generator (GNU m4, GPP, your own one made with ANTLR or GNU bison) to generate some C++ code. Like Qt does with its moc.

So my opinion is that what you want to do is useless. Your unstated goals can be achieved in many other ways. For example, generating "random" looking C++ identifiers (or C identifiers, or ObjectiveC++ names, etc....) like _5yQcFbU0s (this is done in RefPerSys) - the accidental collision of names is then very improbable.

In a comment you state:

Otherwise, I have to use void* for all Objective-C types in the header, but this way I am losing strong typing

No, you can generate some inline C++ functions (that would use reinterpret_cast) to gain again that strong typing. Qt does so! FLTK or FOX or GTKmm also generate C++ code (since GUI code is easy to generate).

My idea was to guard methods with Objective-C types with OBJC macro

This make perfect sense if you generate some C++ or C or Objective C code with these macros.

I suspect if member function pointers or virtual functions are used this will most likely break.

In practice, it won't break if you generate random looking C++ identifiers. Or just if you document naming conventions (like GNU bison or ANTLR does) in generated C++ code (or in generated Objective C++, or in generated C, ... code)

Please notice that compilers like GCC use today (in 2021, internally) several C++ code generators. So generating C++ code is a common practice. In practice, the risks of name collisions are small if you take care of generating "random" identifiers (you could store them in some sqlite database at build time).

also ugly static casts must be added all over the code

These casts don't matter if the ugly code is generated.

As examples, RPCGEN and SWIG -or Bisoncpp- generate ugly C and C++ code which works very well (and perhaps also some proprietary ASN.1 or JSON or HTTP or SMTP or XML related in-house code generators).

The header file is included in both pure C++ and Objective C++ translation units.

An alternative approach is to generate two different header files...

one for C++, and another for Objective C++. The SWIG tool could be inspirational. Of course your (C or C++ or Objective C) code generators would emit random looking identifiers.... Like I do in both Bismon (generating random looking C names like moduleinit_9oXtCgAbkqv_4y1xhhF5Nhz_BM) and RefPerSys (generating random looking C++ names like rpsapply_61pgHb5KRq600RLnKD ...); in both systems accidental name collision is very improbable.

Of course, in principle, using #ifdef guards is not safe, as explained in this answer.

PS. A few years ago I did work on GCC MELT which generated millions of lines of C++ code for some old versions of the GCC compiler. Today -in 2021- you practically could use asmjit or libgccjit to generate machine code more directly. Partial evaluation is then a good conceptual framework.

like image 1
Basile Starynkevitch Avatar answered Oct 17 '22 17:10

Basile Starynkevitch