Ошибки LNK2005 и LNK1169

72
26 августа 2021, 17:10

Пытаюсь поэкспериментировать с файлами в проекте. Выводит ошибки LNK2005 и LNK1169. Вот как выглядят сами ошибки:
LNK2005 "char const * * menus" (?menus@@3PAPEBDA) already defined in main.obj LNK1169 one or more multiply defined symbols found

Заметил что ошибка эта происходит когда я добавляю в mainmenu.h вот этот блок кода

const char* menus[] =
{
    "START",
    "RECORDS",
    "OPTIONS"
};

а если же я его закомментирую, то ошибка пропадает. Вопрос: почему так происходит?

Вот пожалуйста, все исходные файлы ниже. Тут совсем немного кода.

main.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <conio.h>
enum KEY { UP = 72, DOWN = 80 };
bool gameloop();

mainmenu.h

#pragma once
#include <iostream>
#include <conio.h>
#include "main.h"
const char* menus[] =
{
    "START",
    "RECORDS",
    "OPTIONS"
};
int mainmenu(int list = 0);

window.h

#pragma once
#include <Windows.h>
#include <string>
class window
{
private:
    const int width = 100;
    const int height = 40;
public:
    window()
    {
        SetConsoleTitleA("game");
        char command[256]; //буфер для текста команды
        //этот алгоритм нужен чтобы создать системную команду
        //"mode con cols=width lines=height"
        const std::string sys = "mode con cols=" + std::to_string(width) + " lines=" + std::to_string(height);
        std::strcpy(command, sys.c_str());
        //конец алгоритма
        system(command);
    }
};

main.cpp

#include "main.h"
#include "window.h"
#include "mainmenu.h"
int main()
{
    gameloop();
    return 0;
}
bool gameloop()
{
    bool gamestate = true;
    while (gamestate)
    {
        window window;
        mainmenu();
        _getch();
    }
    return true;
}

mainmenu.cpp

#include "mainmenu.h"
int mainmenu(int list)
{
    switch (list)
    {
    case 0:
        cout << *menus[0] << endl;
    case 1:
        cout << *menus[1] << endl;
    case 2:
        cout << *menus[2] << endl;
        switch (_getch())
        {
        case KEY::UP:
            if (list != 0) list--;
            break;
        case KEY::DOWN:
            if (list != 2) list++;
            break;
        }
    }
    return list;
}
Answer 1

Потому что вы определяете (не просто объявляете!) const char* menus[] в заголовочном файле.

Потом, при каждом включении этого заголовочного файла в .cpp-файл, в каждом из них оказывается этот массив.

И какой именно из них линкер должен считать верным и почему? Вот он и теряется...

Answer 2

(Точный ответ на вопрос зависит от того, должен ли этот массив быть глобальной модифицируемой переменной. Из вашего описания этого не ясно.)

(Не ясно также, нужна ли вам вообще глобальная доступность этого массива. Зачем вы вообще поместили его в заголовочный файл, а не в mainmenu.cpp?)

Ваше

const char* menus[] =
{
    "START",
    "RECORDS",
    "OPTIONS"
};

это определение неконстантной переменной, т.е. этот массив имеет внешнее связывание. Если вы включите это в несколько единиц трансляции, то разумеется вы ожидаемо получите множественные определения объекта с внешним связыванием.

  • Либо дайте вашему массиву внутреннее связывание

    const char* const menus[] =
    {
        "START",
        "RECORDS",
        "OPTIONS"
    };
    

    или

    static const char* menus[] =
    {
        "START",
        "RECORDS",
        "OPTIONS"
    };
    

    (что, однако, породит отдельную копию этого массива в каждой единице трансляции)

  • Либо объявите его inline

    inline const char* menus[] =
    {
        "START",
        "RECORDS",
        "OPTIONS"
    };
    
  • Либо идите по "классическому" пути: в заголовочном файле только объявление

    extern const char* menus[];
    

    а определение - в одном из файлов реализации.

Я не знаю, нужно ли вы вам менять этот массив или нет, т.е. можно ли его сделать константным. Если можно, то лучше всего скомбинировать первый и второй варианты и сделать

    inline extern const char* const menus[] =
    {
        "START",
        "RECORDS",
        "OPTIONS"
    };

Разумеется, все это имеет смысл при условии, что объявление массива должно располагаться в заголовочном файле. Из вашего описания не ясно, зачем ему вообще там располагаться.

READ ALSO
Символьный массив

Символьный массив

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

79
Как найти утечку памяти?

Как найти утечку памяти?

Проблема в xPtr, никак не могу найти ошибку, он на нулевом элементе даже

98
Как передаются указатели на переменные при записи/чтении *fstream

Как передаются указатели на переменные при записи/чтении *fstream

Как избавиться от локальных переменных, используя элементы класса в read()?

164