LINUX.ORG.RU

несколько c, один h

 


0

1

Есть несколько *.c файлов, которые включают один и тот же *.h. При компиляции пишет что множественное определение переменной, но почему?

Вот что в *.h

#ifndef HEADER_H
#define HEADER_H
/* здесь разные обьявления */
#endif

Потому что в h переменные нужно объявлять extern. А саму переменную где-нить в c.

anonymous
()

если пишешь там типо
int variable; то когда компилируются разные .c файлы включается много раз одна и таже переменная. Надо говорить что переменная существует, но создавать ее не надо
extern int variable;

linuhs_user
()
Ответ на: комментарий от linuhs_user

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

А я думал, что если определил HEADER_H, то в следующий раз эта область не будет обрабатываться заного.

Да именно int var; Делаю static int var; его использую в другом файле, а при компиляции пишет, что переменная не используется. Если без static, то не компилируется.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от i-rinat

если бы он учился при этом - то его можно было бы понять и простить и помочь, а так это какойто кошмар

Silerus ★★★★
()
Ответ на: комментарий от u0atgKIRznY5

Она не будет, просто на каждый .c файл #define HEADER_H типо сбрасывается

Делаю static int var;

делай как я сказал

linuhs_user
()
Ответ на: комментарий от u0atgKIRznY5

А я думал, что если определил HEADER_H, то в следующий раз эта область не будет обрабатываться заного.

Этот трюк везде и всюду применяется для другого. Если у вас много .h файлов, и определения из одного нужны нескольким, то в таких случаях в .c файле при подключении .h файлов вы получите многократное чтение других. Но если в начале них стоит такой трюк, то он сработает и по условию основное содержимое будет скипнуто.

vodz ★★★★★
()
Ответ на: комментарий от linuhs_user

А в C++ с объектами видимо так не получится сделать... Т.к. при объявлении объекта сразу же происходить его инициализация конструктором... Или все такие есть способ?

Сейчас придумал вариант, создать ссылку на объект в заголовочном файле, а затем где нибудь уже в коде создать сам объект, и указать ссылке этот объект.

neon1ks ★★
()
Ответ на: комментарий от u0atgKIRznY5

При компиляции пишет что множественное определение переменной

Не при компиляции, а при компоновке. Да, при сборке проекта вторая обычно вызывается сразу после первой, но ты их должен отличать.

А я думал, что если определил HEADER_H, то в следующий раз эта область не будет обрабатываться заного.

Во-первых, «заноВо».

Во-вторых, да, не будет. ...Пока ты компилируешь один исходный файл. И в каждом выходном файле (*.o), где включался этот .h, создалась переменная с таким именем. Вот ты их несколько скомпилировал, а потом они попали на вход линкеру. И уже он видит несколько одинаковых глобальных имён и не может их отличить.

Поэтому да, как тебе посоветовали выше, int GlobalValue определяй в *.c, а в *.h напиши extern int GlobalValue, чтобы оно было доступно извне.

Это такое C/C++ - специфичное дерьмецо. В любом языке с нормальными модулями (даже в Object Pascal) таких проблем нет.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

Это такое C/C++ - специфичное дерьмецо.

О, знатоки попёрли... А это ничего, что вот вас предупреждают, что вы сделали глобальное имя, наверное для того, чтобы кто-то в другом месте его юзал, но почему-то перепутали и сделали насколько одинаковых глобальных имён? И это по вашему дерьмо?! Знатоки, такие знатоки...

vodz ★★★★★
()
Ответ на: комментарий от u0atgKIRznY5

Дополню. Страж компиляции (#define HEADER_H) нужен. Но он не распространяется на проект в целом. Это всего лишь директива препроцессора. И #include - это тоже директива препроцессора, которая включает одну простыню текста в другую простыню текста, не более того. И перед компиляцией каждого исходного файла (*.c) эта метапростыня генерируется заново. Есть нюансы (типа прекомпилированных заголовков), но сути процесса они не меняют.

hobbit ★★★★★
()
Ответ на: комментарий от neon1ks

Тоже что ли не волочёшь?

У сборки три стадии: препроцессинг, компиляция, линковка. На первых двух каждый файл обрабатывается отдельно.

Например:

$ cat > 1.h
int i;

$ cat | tee 1.c | tee 2.c > 3.c
#include "1.h"

$ cat > 4.c
int main() {}

$ gcc -c [1234].c
$ ls
1.c  1.h  1.o  2.c  2.o  3.c  3.o  4.c  4.o
$ nm [1234].o

1.o:
0000000000000004 C i

2.o:
0000000000000004 C i

3.o:
0000000000000004 C i

4.o:
0000000000000000 T main

Всё прекрасно откомпилировалось. Каждый объектный файл - отдельная сущность. И в 1 2 3 есть переменная i.

anonymous
()
Ответ на: комментарий от vodz

Излил возмущение? А теперь внимательно прочитай то, что я написал, и больше не приписывай другим людям то, чего они не утверждали. Это по меньшей мере некрасиво.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

А теперь внимательно прочитай то

Я то прочитал внимательно, было б чего читать... С каких пор .h фал уже стал модулем? А что, если завести глобальную переменную не в .h файле, а в .c - проблема исчезнет? Да вообще проблемы нет, ну бывает всякое, может вы руку сбили и написали вместо двух похожих глобальных имён одно и тоже, но вот язык дерьмо... Ну да, ну да.

vodz ★★★★★
()
Ответ на: комментарий от u0atgKIRznY5

А я думал, что если определил HEADER_H, то в следующий раз эта область не будет обрабатываться зано

Нет. Если у тебя сложная иерархия хедеров и может быть так, что посредством других хедеров некоторый хедер может быть включен два раза (что вполне логично). Так вот этот «include-guard» не позволяет два раза заинклюдить хедер путем деактивирования его содержания. Например: Common.h

#pragma once // убери и не будет коспилироваться

struct Common {
    int a;
    int b;
};
Foo.h
#pragma once

#include "Common.h"

struct Foo {
    union {
        unsigned long contents;
        struct Common common;
    };
};
Bar.h
#pragma once

#include "Common.h"

struct Bar {
    int isOwned;
    struct Common *common;
};
main.c
// в обеих случаях при препроцессинге надо будет включить "Common.h" 
#include "Foo.h"
#include "Bar.h"

int main() {
    struct Foo f;
    struct Bar b;
    b.common = &f.common;
    b.isBorrowed = 1;
    f.contents = 0x0000ffff0000ffff;
    return 0;
}

KennyMinigun ★★★★★
()
Ответ на: комментарий от vodz

С каких пор .h фал уже стал модулем?

А я и не говорю, что он модуль. В том и дело, что не модуль.

А что, если завести глобальную переменную не в .h файле, а в .c - проблема исчезнет?

Да, в контексте ОП - исчезнет.

язык дерьмо

Повторяю ещё раз: я этого не утверждал. Дерьмо - это костыли, которые городят в C и C++ вместо отсутствующей в них модульности. Из-за этого приходится два раза писать имя одной переменной: один раз с extern, другой без. И это стандартная практика. В том же объектном паскале достаточно поместить переменную в секцию interface, чтобы её видели другие модули.

Проблема топикстартера в том, что он, видимо, не до конца разобрался с участниками процесса сборки (препроцессор, компилятор, компоновщик).

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

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

Да, в контексте ОП - исчезнет.

В смысле? cin заведенный в libc++ отличается от вашего cin из соседнего c++ файла? Ну да, ваш явно какоу-то глюк, только проблема то вот она.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Ну да, ваш явно какоу-то глюк, только проблема то вот она.

Это на каком языке?

В смысле? cin заведенный в libc++ отличается от вашего cin из соседнего c++ файла?

При чём здесь cin? Ещё раз ОП перечитайте. У ТСа проблема - ему надо переменную для доступа извне определить один раз, и он не понимает, как это правильно сделать. И это не только его вина, поскольку в C и C++ это действительно делается не совсем тривиальным образом.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

Это на каком языке?

Что, ума палата не достаточно, чтобы увидеть, что это описка печати у->й?

При чём здесь cin?

При том, что он глобален.

У ТСа проблема

Вы тему то не меняйте. Ему уже всё разжевали, а обсуждаем тут ваше обощение насчёт «в C и C++ это действительно делается не совсем тривиальным образом.» Что явно от небольшого ума. Вам просто не нравится, что ваша тривиальная ошибка не самоустраняется компилятором как нянькой. Не понимая, что в других случаях эта «я знаю лучше что вам на самом деле хочется» будет чудовищно бесить.

vodz ★★★★★
()
Ответ на: комментарий от neon1ks

А в C++ с объектами видимо так не получится сделать... Т.к. при объявлении объекта сразу же происходить его инициализация конструктором... Или все такие есть способ?

Для определений (а не объявлений) в заголовках служит inline. Линковщик сам выберет одно определение (любое) и удалит все остальные. Для функций это работает во всех стандартах C++, начиная с первого. Для переменных inline работает только начиная с C++17. Для pre-C++17 можно определение переменных делать статическими членами шаблонной структуры например, шаблоны также неявно инстанцируются как inline начиная с первого стандарта.

См. https://www.reddit.com/r/cpp/comments/79c1pn/cppcon_2017_nir_friedman_what_c_...
и https://github.com/Jajauma/GlobalsDemo

PS. Слайды у докладчика с ошибками, я ему об этом в том треде написал, и он согласился, и сказал что исправит у себя на сайте.

d_a ★★★★★
()
Ответ на: комментарий от d_a

Слайды у докладчика с ошибками

Завидую вот таким, кому не лень в этом копаться, честно. :)

А то что inline для функций работает, так у gcc для static inline и в сишке было всегда, как только сам «inline» появился. Но вот не static уже не понятно, что хотят собственно добиться экспортируя одинаковые имена? Зачем чинить то, что не сломано? «взять первое попавшееся» — это же какой-то сюр.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)

У меня есть подозрение, что ТС — нейросеть, которая обучается писать код. И это ещё первые итерации.

a1batross ★★★★★
()
Ответ на: комментарий от vodz

Я не в курсе как оно в C. В C++ inline означает одно -- разрешить более чем одно определение в программе, при условии что они одинаковы. Нужно это в первую очередь для header-only библиотек. (Определения со static в заголовках раздуют программу из-за internal linkage, в каждой единице трансляции будет свой дубль со своим адресом, что очевидно не то что нужно.)

d_a ★★★★★
()
Последнее исправление: d_a (всего исправлений: 2)
Ответ на: комментарий от a1batross

Предлагаю учить неправильно, чтобы job security.

d_a ★★★★★
()
Ответ на: комментарий от vodz

В смысле?

ОП это Орижинал Пост, а не опечатка в «ООП».

i-rinat ★★★★★
()
Ответ на: комментарий от vodz

С каких пор .h фал уже стал модулем?

Такое ощущение, что ты на все остальные языки смотришь сквозь призму Си.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Такое ощущение, что ты на все остальные языки смотришь сквозь призму Си.

Что, даже на sed? :) На самом деле, я, конечно, не в курсе, как там будет определяться в c++21 (ничего не перепутал?) где модуль, а где .hpp, мало ли что сейчас придумают модно-молодёжно, но я всегда считал, что .hpp кроме чисто общестилистических соглашений от .cpp не отличается и ровно так же как и в C. Потому вопрос, если я сделаю руками #include, то есть вот просто возьму файл в буфер редактора, удалю строчку #include «my.hpp» и заменю на содержимое буфера, то получится отличие? Да? Если так — то действительно в этом мире что-то стало совсем по-другому...

vodz ★★★★★
()
Ответ на: комментарий от neon1ks

Точно так же. Екстрн. А инит паратметр в спп файле

anonymous
()
Ответ на: комментарий от vodz

Ну вот, я же говорил. Тебе про другие языки говорят, а ты зациклился на Си и Си++.

i-rinat ★★★★★
()
Ответ на: комментарий от vodz

Что явно от небольшого ума.

Переход на личность оппонента характеризует не столько оппонента, сколько Вас. Но ладно, покормлю ещё.

Вам просто не нравится, что ваша тривиальная ошибка не самоустраняется компилятором как нянькой.

Ну вы уже определитесь. Либо самоустраняется, либо компилятором. :)

Если серьёзно - про какого рода ошибку речь? Если про ту, которую у ТС - она от недопонимания процесса сборки. Но на самом деле здесь ещё закладывается избыточность, которая может привести к более глубоким ошибкам. Представьте себе, что на какой-то стадии extern int globalValue переименовали, а та, которая в сишнике, осталась нетронутой. Сообщение об ошибке будет, конечно, только вот увидеть его связь с источником может оказаться непросто.

Не понимая, что в других случаях эта «я знаю лучше что вам на самом деле хочется» будет чудовищно бесить.

Вы опять занимаетесь телепатией. Где у меня «я знаю лучше что вам на самом деле хочется», не покажете?

Я объяснил, какую модульность я считаю хорошей - это когда модуль является не только файлом, но и лексической единицей, со своими проверками. В C и C++ не так.

Вот одно из следствий (не единственное). Если я в паскале забуду включить в uses нужный мне модуль - я гарантированно получу сообщение об ошибке. Ибо uses контролируется компилятором и не обладает транзитивностью. Если же я в C или C++ забуду написать #include, то может случиться так, что на одной версии используемой библиотеки программа соберётся, а на другой вывалит ошибку.

Это потому, что в первом случае я из своего example.cpp вызвал lib1.h, а тот, в свою очередь, включает lib2.h, в котором по случайному совпадению, определена нужная мне функция/класс. А в другой версии lib API оставили, но включаемые файлы отвязали друг от друга. И в общем-то, авторы библиотеки имеют на это право, поскольку порядок вхождения заголовочных файлов друг в друга - это такая внутренняя кухня, которая обычно даже не документируется толком. Единственный способ обеспечить однозначность здесь - это самому следить за тем, чтобы в прикладном проекте были написаны #include для всех имён, которые должны быть разрешены. Ошибиться тут очень легко (и не только новичку).

Приведённый пример - не академический, я такую ситуацию встречал в проекте на C++/Qt, который должен был собираться на трёх разных ОС, и Qt и gcc там были разных версий. Код писали три программиста. На двух платформах собирается, на третьей не хочет.

Я не хочу, чтобы компилятор исправлял ошибки за меня (если Вам так показалось). Я хочу, чтобы сообщения об ошибках были предсказуемы и однозначны. Это тоже, по-вашему, называется «я знаю лучше что вам на самом деле хочется»?

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

Переход на личность оппонента характеризует не столько оппонента, сколько Вас.

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

Вы опять занимаетесь телепатией. Где у меня «я знаю лучше что вам на самом деле хочется», не покажете?

Да в каждом же сообщении.

Представьте себе, что на какой-то стадии extern int globalValue переименовали, а та, которая в сишнике, осталась нетронутой. Сообщение об ошибке будет, конечно, только вот увидеть его связь с источником может оказаться непросто.

В заголовочном файле будет больше ещё один ненужный в данном проекте описанный внешний символ. Какая же это проблема? В исходнике вы не сможете теперь обратиться к этой переменной, если она там внешняя. Вам придётся или читать документацию на изменившийся API или писать баг-репорт на ошибку (скорее всего описку) в заголовочном файле. Чем тут Паскаль поможет — решительно не представляю.

Я хочу, чтобы сообщения об ошибках были предсказуемы и однозначны.

Что там неоднозначного? Символ найден там и сям.

vodz ★★★★★
()
Ответ на: комментарий от vodz

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

Я «обзывал» не человека. И даже не язык, а конкретное свойство этого языка - отсутствие модульности. Чувствуете разницу?

Возможно, для середины 70-х такой подход был нормален, но уже в начале 90-х он вызывал недоумение, а сейчас так откровенно припахивает.

Чем тут Паскаль поможет — решительно не представляю.

Хотя бы тем, что там имя будет определено 1 раз, а не 2.

Ну и комментарий по поводу ситуации «lib1 включает lib2» тоже хотелось бы получить.

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

а сейчас так откровенно припахивает

Может было чем и без этого занятся? Давайте повангую. Если бы не анонсировали модульность в ближайшем стандарте плюсов, про это вообще бы никто не вспомнил. Возвращаясь к нашим баранам, модульность у ТСа вовсе не при чём. Он либо должен сделать локальное имя, либо глобальное в одном месте. А в заголовочном файле не декларировать, а размещать — не от недопонимания языка, а вообще мироздания. Все ручки для этого даны. Ну чем тут поможет модульность? Модули же по идее так же должны возмутится о перекрытии имен, если не юзается префикс?

vodz ★★★★★
()
Ответ на: комментарий от vodz

Должны. Только в ситуации ТСа перекрытия бы и не было, поскольку переменная была бы объявлена в одном месте (к примеру, для того же Паскаля - в секции interface). И физически это означало бы то же самое, что в Си сочетание «размещаем в *.c, декларируем в *.h», только короче и с меньшим количеством возможностей выстрелить себе в ногу.

Если же в Паскале переменную объявить в implementation, она опять-таки будет глобальной, только не будет торчать наружу.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

Ладно, завязываем оффтопик. Паскаль я откровенно забыл, никаких тёплых чувств к нему по сравнению с C у меня не вспоминается, помню уродливые указатели, дичайший интерфейс при работе с файлами и прочий бейсик, не позволяющий написать в if присваивание и т п. Нафиг.

она опять-таки будет глобальной, только не будет торчать наружу.

В смысле чтобы библиотечный символ не переписать? Есть такая проблема, решается каждой devel-системой по своему, всякие objcopy и прочее, но желающих оградить от выстрела в ногу надо посылать подальше. В библиотеках решается всякими weak-ами, а в пользовательском окружении можете сколько угодно писать свой хоть new.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от hobbit

Это такое C/C++ - специфичное дерьмецо. В любом языке с нормальными модулями (даже в Object Pascal) таких проблем нет.

Неправда. В С++17 можно объявлять inline переменные в заголовочных файлах. Специально сделано для header-only библиотек.

var.hpp

#pragma once

inline int a = 5;

auto v() -> void;
main.cpp
#include "var.hpp"
#include <iostream>

auto main() -> int
{
    std::cout << a << std::endl;
    v();
}
v.cpp
#include "var.hpp"
#include <iostream>

auto v() -> void
{
    std::cout << "we are in v(): " << a << std::endl;
}

g++-7 -std=c++1z main.cpp v.cpp 
./a.out
5
we are in v(): 5
g++-7 -v
gcc version 7.2.0 (Ubuntu 7.2.0-1ubuntu1~16.04)
fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

В С++17 можно объявлять inline переменные в заголовочных файлах.

Что особенно забавно, необходимая машинерия для этого была у нас с самого начала (C++98), так как inline-функции (явно), функции-члены, определённые в объявлении своего класса (неявно) и специализации шаблонов (неявно) именно так и работают (та или иная разновидность слабого символа).

d_a ★★★★★
()
Ответ на: комментарий от vodz

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

Регулярно вспоминают, в том числе, вспоминали в новости о принятии С++17, в котором модульность так и не добавили. Модульность, которая начала появляться в языках программирования с конца 70-х.

не позволяющий написать в if присваивание

что только люди не делают, лишь бы не использовать while.

уродливые указатели

они везде не красавцы.

grem ★★★★★
()
Ответ на: комментарий от fsb4000

#pragma once

Это теперь стандартная директива?

Похоже, что нет и непонятно почему её до сих пор не сделали стандартной? о_О

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

Регулярно вспоминают, в том числе, вспоминали в новости о принятии С++17, в котором модульность так и не добавили.

Про вспоминание имелось в виду в этом топике. Если б не планировали вот-вот... и далее по тексту.

Модульность, которая начала появляться в языках программирования с конца 70-х.

Тогда и сейчас это надо языкам, где модуль расширяет возможности самого языка, особенно когда такое расширение средствами самого языка сделать невозможно. Если б вам захотелось сделать свой, ну скажем «my_write» на паскале, что-нибудь системное на perl и так далее — это же принципиально невозможно, по крайней мере на тех диалектах из 70-х. А на самодостаточных языках модульность не бог весь какая срочная потребность, потому и до сих пор протянули.

что только люди не делают, лишь бы не использовать while.

Чо? Ну афигеть... Давайте вы будете заменять в русском языке «если» на слово «пока».

они везде не красавцы

А сдаётся, мил человек, что у вы ими ушиблены. После изобретения (void *) указатели стали законченной красотой. Те кто их всячески избегает по моему принципиально не высококлассный программист, ибо он избегает метод точно отражающий семантику работы с памятью, алгоритмические переключатели (указатели на функции) и так далее.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

Давайте вы будете заменять в русском языке «если» на слово «пока».

Если с присвоением в условии превращается в фактически в «пока», не? Делать они будут одно и то же, если судить по тому, какие примеры мне попадались.

Может ещё в условие в виде присвоения тяжёлые конструкторы запихивать все будут?

ибо он избегает метод точно отражающий семантику работы с памятью, алгоритмические переключатели (указатели на функции) и так далее.

Емнип, в в каком-то диалекте Паскаля уже были безтиповые указатели, вводимые с помощью просто ключевого слова pointer.

У кого-то видел в паскалевской программе как-то «указатели» на разные процедуры в зависимости от условия в виде ассемблерных вставок. Можно ли было иначе, не знаю.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от vodz

Чо? Ну афигеть... Давайте вы будете заменять в русском языке «если» на слово «пока».

я перепутал if и for :)

grem ★★★★★
()
Ответ на: комментарий от anonymous

$ cat > 1.h
int i; /* <-- обявление либо определение (на этапе компиляции неизвестно) */

$ cat | tee 1.c | tee 2.c > 3.c
#include "1.h"

$ cat > 4.c
int main() {}
int i = 10 ; /* <--- определение */

$ gcc -o some -W -Wall -pedantic -ansi -D NDEBUG *.c

/* после линковки, i == 10 в 1.c, 2.c, 3c, 4c */

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.