Борьба бобра с ослом, или адаптация MSVC кода под gcc

в 7:02, , рубрики: c++, gcc, MSVC, Блог компании EXANTE

Статья описывает некоторые затруднения, которы мы встретили при попытке адаптации одного из наших старых Windows-only проектов (плагин к MT4 серверу) к кросскомпиляции под Linux (CI, статический анализ, автотесты и прочие модные слова). Точнее, в коде присутствовал ряд конструкций, которые спокойно съедались MSVC, но категорически отказывались компилироваться с использованием mingw/gcc.

image

Под катом 7 наиболее часто встретившихся примеров кода, которые будут компилироваться MSVC, но не будут с gcc, и способы это лечить.

Дисклеймер

Цель статьи – не сказать, что какой-то компилятор лучше, чем другие, а указать на некоторые проблемы, которые могут возникнуть при адаптации кода к другим компиляторам (особенно если до этого использовался только MSVC). Также некоторые (если не все) элементы поведения можно свести к одному, если подкрутить флаги компиляции, но ведь лучше все-таки поправить код (хотя бы и sed'ом), правда?

Условия задачи

Имеем среднего размера проект (около 15к SLOC не считая библиотек), в котором используется CMake с практически дефолтными флагами компиляции. MSVC используем 14 версии, а mingw-gcc — 6.3.

Найденные проблемы

Декорация имен методов

Внутри нашего проекта присутствует несколько методов, которые должны вызываться как C методы для того, чтобы плагин распознавался сервером. Оригинально в коде использовались следующие конструкции:

__declspec(dllexport) void SomeMethod() {}

При компиляции gcc имя функции декорировалось, что приводило к тому, что сервер не определял метод в плагине. Более правильное (рабочее) решение:

extern "C" __declspec(dllexport) void SomeMethod() {}

Пути к файлам и include

Различие разделителей в путях на разных системах также приводит к ошибкам на этапе компиляции. Код

#include "directory\include.h"

откажется компилироваться под Linux/gcc, хотя под Windows/MSVC никаких проблем не будет. Это не совсем ошибка, но следует отметить, что для удобства переносимости лучше все же использовать обычный слэш, поскольку он воспринимается большинством систем. С путями также есть и другая проблема...

Регистр и include

Как вы, вероятно, знаете, пути some/path и SoMe/pATh в Windows не различаются, но это не так в некоторых других системах, что приводило к ошибкам, если программист указывал путь в заголовочному файлу без учета регистра. Например:

#include <Winsock2.h>

выдаст ошибку с gcc под Linux, потому что указанный файл просто не будет найден. Аналогичная проблема также наблюдается с именами библиотек, например, Ws2_32 против ws2_32.

Как определить целевую платформу

В проекте активно используется QuickFIX, который, как предполагается, должен компилироваться и работать под разными системами. В актуальной версии QuickFIX используются следующие конструкции:

#ifndef _MSC_VER
#include <unistd.h>
#endif

Не надо так делать. При использовании mingw _MSC_VER не определяется, вместо этого правильнее проверять _WIN32 для определения целевой платформы, а _MSC_VER использовать, только если вы хотите включить код, специфичный для MSVC.

Pure virtual методы

Код

class SomeClass
{
  virtual void someMethod() = NULL;
};

при попытке компиляции gcc радостно скажет

invalid pure specifier (only «= 0» is allowed)

но не вызовет ошибок у MSVC. Причина проста: gcc раскрывает макрос NULL не в 0, а в __null (что, в общем-то, совсем не запрещено). Решение: очевидно, отказаться от использования NULL для указания pure virtual методов и использовать = 0.

Определение методов внутри заголовочного файла

Код

class SomeClass
{
  SomeClass::SomeClass() {};
};

при использовании gcc выдаст

extra qualification ‘SomeClass::’ on member ‘SomeClass’

Правильный ответ, очевидно, не должен содержать SomeClass::. Вообще, в драфте стандарта C++14 (параграф 8.3) написано, что:

the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers

Декларация переменной без указания переменной

Код, написанный с использованием клипбордного интерфейса

void someMethod()
{
  SomeClass;
  SomeClass class;
}

содержит в себе ошибку, которая игнорируется MSVC, но вызовет ошибку на этапе компиляции у gcc:

declaration does not declare anything

Не уверен, какое поведение здесь правильное, а MSVC, вероятно, просто вырезает неиспользуемую строчку без подачи каких либо сигналов.

Вместо послесловия

Большинство перечисленных мной ошибок, очевидно, довольно легко правятся и без чтения данной статьи, руководствуясь исключительно замечаниями компилятора. Однако она наглядно иллюстрирует различие «двух миров» в восприятии вашего исходного кода, и то, что для вас может быть естественным, отнюдь не является таковым при смене компилятора.

Автор: EXANTE

Источник

Поделиться

* - обязательные к заполнению поля