1

I am a student and I am currently taking a compiler construction course. I develop my compiler in C++ on Ubuntu, using GCC and CMake. While everything works fine on my development machine, the code broke horribly when compiled against MSVC or Visual Studio 2017 on the school test. As I tracked down the error messages thrown by MSVC, I noticed there are several issues causing these failures:

  • GCC's headers give you more stuffs than MSVC does.
  • Some other weird errors that I can't explain (Sorry but I can find a better word).

To ground the discussion, see the following examples, which work for GCC but break on MSVC.

#include <iostream>

int main() {
  std::string S;
  std::getline(std::cin, S);
}

MSVC requires #include <string> to work.

#include <unordered_map>

int main() {
  std::min(1, 2);
}

MSVC requires #include <algorithm> to work.

These issues are not hard to fix once they break out: I can just #include the required headers. However, it is too expensive to let them break out. It takes away my scores and hurt me.

I have no interest in discussing which compiler is more standard-compliant. I simply want to catch any portability issue before they catch me. But I am not well-informed of all those tricky differences between these compilers. That's why I am asking for tools to catch these issues. I want to update my tool chains to write more portable code.

Edit: Besides tools, I will humbly learn any code disciplines, good practices and known issues in writing portable code.

Edit: Sorry folks. I am not meant to make the second example wrong (just deleted). I just have no access to a MSVC to reproduce the problems.

Edit for Clarification: What this post is not for:

  1. How to fix certain issues when porting code from GCC to MSVC.
  2. How to write standard-compliant C++ code that universally compiles.

In fact, this post is asking for practical actions one can take to port code from GCC to MSVC or better, to write code without portability issues from the beginning. The examples in this post are used to make discussion concrete or to show the actual difficulty, but not comprehensive. I don't think there is a single truth about this question, but I'd like to try out some good ones.

cgsdfc
  • 538
  • 4
  • 19
  • 7
    You shouldn't write your code to target any specific compiler at all. In other words, include the headers you use, don't rely on a compiler version to include a header upstream for you. – Cory Kramer Dec 10 '18 at 14:53
  • 1
    Both behaviors are conformant. If you are using string, include string. Same for everything else. – Matthieu Brucher Dec 10 '18 at 14:54
  • On MSVC, you have `/fpermissive-` that removes all non portable stuff. And I thin kthere are similar flags for gcc and clang as well. – Matthieu Brucher Dec 10 '18 at 14:55
  • What version of MSVS are you using? Your third code block works just fine on 15.8.7. – NathanOliver Dec 10 '18 at 14:57
  • `-pedantic-errors` for gcc and clang to give errors on non-standard-conform behavior. Also `-std=c++XX` instead of the default `-std=gnu++XX` to disable extensions. –  Dec 10 '18 at 14:58
  • @NathanOliver sorry, that code block may be wrong. I will adjust it. – cgsdfc Dec 10 '18 at 14:59
  • last example works for [MSVC](https://rextester.com/live/ACUS82264). – Marek R Dec 10 '18 at 15:02
  • @user10605163 Yes I use ``set(CMAKE_CXX_STANDARD 17)`` in ``CMakeLists.txt``. But can ``-pedantic-errors`` detect header issues? – cgsdfc Dec 10 '18 at 15:08
  • The only think you can do is configure your build in such way that all compilers used during build process. There are more differences between compilers, some with manifestation during run time. So you have to run tests for each compiler anyway. – Marek R Dec 10 '18 at 15:10
  • For what it is worth, I have been looking for a "missing header" tool for quite some time. There's a couple questions on Stack Overflow asking for the same. I have not found a good solution. Also see [Tools to find included headers which are unused?](https://stackoverflow.com/q/1301850/608639), [Detecting superfluous #includes in C/C++?](https://stackoverflow.com/q/614794/608639), [How to generate a list of missing #include files](https://stackoverflow.com/q/13417745/608639) and [Static analysis of header inclusion in C++](https://stackoverflow.com/q/11133222/608639) – jww Dec 10 '18 at 15:11
  • @cgsdfc No, because the implementation is allowed to have header files include more than required by the standard. `-pedantic-errors` warns/errors only in cases where the standard requires the program to be ill-formed, but gcc would compile anyway using a non-conforming extension, e.g. variable-length arrays. You can lookup the header required by every standard library entity in the standard or a reference like cppreference.com –  Dec 10 '18 at 15:12
  • John Lakos' 3rd rule for physical design: no transitive includes https://youtu.be/K_fTl_hIEGY?t=1485 – AndyG Dec 10 '18 at 15:13
  • I wonder why no body mention the use of IDE, i.e., ``CLion``. My friend told me once your code works on `CLion`, it almost work on MSVC. I am not sure about it. – cgsdfc Dec 10 '18 at 15:19
  • @cgsdfc You really don't need a better tool chain. You need to make sure you include the headers of the things you use. In this case I would say MSVS is the "best", since it's standard headers include less, so you get errors when you don't do what you are supposed to. – NathanOliver Dec 10 '18 at 15:22
  • @NathanOliver Yes, I will check more carefully against headers inclusion. In terms of better tool chain, I think I lack the most important part: the compiler I am targeting (MSVC). The compiler can detect any invalid code (better than any static checker I think). – cgsdfc Dec 10 '18 at 15:37
  • @AndyG quite right. Python, Java and other languages use ``import``-similar construct to prevent transitively importing symbols from other TUs. But with C++, ``#include`` is transitive in nature. – cgsdfc Dec 11 '18 at 07:35

1 Answers1

2

Compile against multiple compilers

When comes to portability there are two approaches. One charming, idealistic but non-practical: where you wish to write code that is absolutely portable: i.e. to work as expected on any compiler, real or imaginary, present and future. You might be tempted to say: hey that's just standard C++ code. Unfortunately compilers are software and as any complex software they have bugs. Moreover, the standard has bugs (where defect-reports are applied retroactively). The more you write complex code the more you will encounter these bugs.

The other approach is a practical one. Instead of aiming for 100% portable code, you aim for portability on a set of versions of compilers on a set of architectures. For instance you can aim for your code to work on x64 linux and windows, gcc clang and msvc, latest version , or from version x upwards and ignore everything else. (icc be damned). To achieve this there is really just one way: test your code on all these platforms. For this at minimum you need to create unit tests for your code and then compile and run these tests on all the architectures and compilers. You can do this manually or automate the process (e.g. CI).

Running your code on multiple compilers you will find that you need to modify the code to be more standard compliant or write different code for different compilers and versions to go around bugs or limitations. SO is full with compliant code that doesn't work on some major compiler (version) or another.

Compile with appropriate switches

Compilers have custom extensions (some enabled by default). You should disable them. It's compiler specific. For gcc and clang for example you need -pedantic. I don't know for msvc. But even these are not enough:

Some users try to use -Wpedantic to check programs for strict ISO C conformance. They soon find that it does not do quite what they want: it finds some non-ISO practices, but not all—only those for which ISO C requires a diagnostic, and some others for which diagnostics have been

Use tools complementary to compilers

Use static analyzer tools. These tools analyze your code and catch some cases of bugs, illegal or Undefined Behavior code.

Also take a look over clang sanitizers.

Don't forget about the standard

You say you are not interested in a discussion about which one is more standard compliant, but you should at least be interested in standard compliant code. For instance in your first example even if it happens to work on your version of gcc the code is illegal because including <string> and <algorithm> is mandated by the standard here.

When you encounter code that works differently on different compilers you definitely should investigate and see what is standard compliant.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • 1
    I don't get this answer. The school of though should be to use the includes that relate to what you write. If you use a standard algorithm, you should `#include `. Simple as that. – NathanOliver Dec 10 '18 at 15:14
  • Without much experience surely I will take a practical approach. But that would require me having an instance of MSVC running on the computer, which I can't for some reason. Does Microsoft provides a CI for MSVC build? – cgsdfc Dec 10 '18 at 15:22
  • @NathanOliver I got that idea but I just take for granted to write the buggy code. And I failed to catch it with test. – cgsdfc Dec 10 '18 at 15:24
  • @cgsdfc you can use VMs or dockers or clound solution (although those are not free) – bolov Dec 10 '18 at 15:25
  • @NathanOliver I don't address these examples in particular, but the root of the problem: how do you know when you have written a non-portable code, even when it appears to work. – bolov Dec 10 '18 at 15:28
  • @NathanOliver gcc and clang will happily compile the string code with all the conceivable switches enabled. And it is entitled to do so because of the "no diagnostics required". A good way to find these kind of situations is to compile against multiple compilers. – bolov Dec 10 '18 at 15:31
  • As the question is now on hold, let's sum it up. I want to write standard-compliant code in general. I want to target ``VS 2017`` in particular. I will take your advises to: 1. statically check my code, either with tools or manually with doc. 2. try to install the target compiler and test on it. Thanks folk. Really help. – cgsdfc Dec 10 '18 at 15:43