Статья описывает некоторые затруднения, которы мы встретили при попытке адаптации одного из наших старых Windows-only проектов (плагин к MT4 серверу) к кросскомпиляции под Linux (CI, статический анализ, автотесты и прочие модные слова). Точнее, в коде присутствовал ряд конструкций, которые спокойно съедались MSVC, но категорически отказывались компилироваться с использованием mingw/gcc.
Под катом 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, вероятно, просто вырезает неиспользуемую строчку без подачи каких либо сигналов.
Вместо послесловия
Большинство перечисленных мной ошибок, очевидно, довольно легко правятся и без чтения данной статьи, руководствуясь исключительно замечаниями компилятора. Однако она наглядно иллюстрирует различие «двух миров» в восприятии вашего исходного кода, и то, что для вас может быть естественным, отнюдь не является таковым при смене компилятора.