LINUX.ORG.RU
ФорумTalks

Инопланетяне среди нас!

 


0

3

Давным-давно, изучая С++, столкнулся со странным поведением в определении статических переменных класса: их следовало объявить внутри класса, а затем обязательно(!) зачем-то определить за его пределами. В ту пору я не придал этому большого значения. Как оказалось, зря. Шли годы, и чем больше я программировал на С++, тем более странной мне казалось решение комитета по стандартизации. «Ну что ж, каждый может иногда совершать чудачества и делать странные вещи» - мыслил я. Но вот теперь вышел стандарт С++11, уже стало разрешено определять обычные переменные внутри класса за пределами конструктора, но в последних компиляторах, данный способ инициализации статических переменных, всё ещё прибит гвоздями. Теперь стало очевидно: это не банальная ошибка. Здесь кроется что-то большее.

Когда-то давно ко мне пришла такая мысль: если бы инопланетяне существовали среди людей, как можно было бы их отличить от людей, при условии, что они могли бы за счёт своих технологий создавать иллюзию внешнего вида, полностью неотличмого от внешнего вида человека (вкл. речь, одежду, и т.п.). Как доказать их существование? Как отличить от обычных людей? Ответ был очевиден: по поведению!

Несомненно, инопланетный образ мышления должен в корне отличаться от человеческого. Несомненно, что вынос определения статических переменных, да ещё и закреплённый в самых последних стандартах, противоречит самым основам человеческой логики. Даже какой-нибудь эльф, дракон, или вампир (если бы они существовали), скрытно живущий в человеческом, не смог бы додуматься до такого. Нет! Здесь что-то ещё, какая-то хитрость. Только пришелец из других миров мог обладать _настолько_ не стандртным мышлением, чтобы изобрести столь необычный способ объявления переменной.

Очевидно, что инопланетяне существуют, и часть из них заседает в комитете по стандартизации С++!

★★★★★

линукс здесь при том, что его собирают gcc, в котором сабжевое поведение даже опциями компилера не отключаемо

next_time ★★★★★
() автор топика

инопланетяне это лисп и его производные, остальное еще в рамках здравого смысла

umren ★★★★★
()

А это не часть наследия языка Си?

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

ien
()

Все правильно. Обьявления классов не создают в образе запущеного приложения ровно ничего. Реализации методов класса хоть точки входов генерируют. Статическая переменная - переменная, которая тоже требует места в сегменте данных, потому ее нужно определить в каком-то срр файле чтобы получить из него *.о файл у которого будет задекларирована вот такая вот переменная.

Если обьявить статическую переменную просто в хедере при декларации класса, а хедер включить в 100500 файлов, то в них в каждом нужно выделять место под переменную? Они то просто не знают еще слинкуют их вместе или нет? Можно конечно везде нагенерить, потом как-то дедуплицировать... По сути проблема ущербного использовани хедер-файлов

Чорд, да я же не с Земли!

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

данный способ инициализации статических переменных, всё ещё прибит гвоздями.

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

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

Статические переменные они же глобальные

Это с чего вдруг?

Статические переменные вполне могут и локальными быть.

K&R - русское издание 2001 г. - раздел 4.6, стр.113, верхний абзац.

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

А это не часть наследия языка Си?

в Си вообще полноценных классов не было, и, как следствие, статических переменных внутри

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

так ведь глобальные переменные можно определять прямо в месте объявления

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

так ведь помимо определения их всё равно приходится объявлять, со всеми вытекающими

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

Если обьявить статическую переменную просто в хедере при декларации класса, а хедер включить в 100500 файлов, то в них в каждом нужно выделять место под переменную? Они то просто не знают еще слинкуют их вместе или нет? Можно конечно везде нагенерить, потом как-то дедуплицировать... По сути проблема ущербного использовани хедер-файлов.

Чорд, да я же не с Земли!

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

а хедер включить в 100500 файлов, то в них в каждом нужно выделять место под переменную?

а ещё вы неосилятор #pragma once

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

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

next_time ★★★★★
() автор топика

Есть декларация «такая переменная есть». И есть то место где она на самом деле находится и инициализируются. На самом деле там всё очень логично.

ranka-lee
()
Ответ на: комментарий от next_time

a.h

#pragma once

class Cookie{
public:
    static int k;
};

int Cookie::k = 0;

b.h

#pragma once

int inc_b();

b.cc

#include "a.h"

#include "b.h"

int inc_b(){
    return Cookie::k ++;
}

c.h

#pragma once

int inc_c();

c.cc

#include "a.h"

#include "c.h"

int inc_c(){
    return Cookie::k ++;
}

main.cc

#include "a.h"

#include "b.h"

#include "c.h"

#include <iostream>

using namespace std;

int main(){
    cout << "inc_b " << inc_b() << endl;
    cout << "inc_c " << inc_c() << endl;
}

Конпеляю

$clang++ -c c.cc -o c.o
$clang++ -c b.cc -o b.o
$clang++ -c main.cc -o main.o
$clang++ *.o -o main
c.o:(.bss+0x0): multiple definition of `Cookie::k'
b.o:(.bss+0x0): first defined here
main.o:(.bss+0x0): multiple definition of `Cookie::k'
b.o:(.bss+0x0): first defined here
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Вот это ты имел ввиду?

а ещё вы неосилятор #pragma once

facepalm.cpp

Это ты совсем в лужу

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 2)
Ответ на: комментарий от ranka-lee

И есть то место где она на самом деле находится

для «на самом деле» используют ассемсблер, а не ООП ЯП

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

Статические переменные вполне могут и локальными быть.

K&R - русское издание 2001 г. - раздел 4.6, стр.113, верхний абзац.

В оригинальном сообщении речь шла про статические поля в классе, а не про статические переменные в Си.

Они там «локальные для функции», это просто глобальная переменная с ограниченной областью видимости.

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

в Си вообще полноценных классов не было, и, как следствие, статических переменных внутри

Наследие языка Си выражается в логике работы с глобальными переменными.

так ведь глобальные переменные можно определять прямо в месте объявления

Их нельзя объявлять в заголовочных файлах, потому что это предписывает компилятору создать переменную в каждой единице трансляции (*.cpp) включающей этот заголовочный файл. Что вызывает ошибку линковки — в разных модулях (единицах трансляции) появляются глобальные переменные с одинаковыми именами.

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

так ведь помимо определения их всё равно приходится объявлять, со всеми вытекающими

Для объявления глобальной переменной из другого модуля используется модификатор extern.

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

а ещё вы неосилятор #pragma once

Не, это вы неосилятор С++.

Схема такая:

a.h --> a.cpp
    \-> b.cpp

Т.е. a.h включается с помощью #include в a.cpp и b.cpp. В a.h объявлена глобальная переменная g.

Компилятор транслирует каждый cpp по отдельности, при компиляции a.cpp получается a.o в котором есть переменная g. При трансляции b.cpp получается b.o, в котором есть переменная g.

В итоге этих переменных две. Если бы cpp-файлов было 100500, то было бы 100500 переменных g.

Линковщик их не слинкует, если вы не объявите переменную g, как static.

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

В C++ типа-ООП это просто надстройка над кроссплатоформенным ассемблером.

ranka-lee
()
Ответ на: комментарий от ien

Не, это вы неосилятор С++.

не больше, чем вы: прагма once не включена в стандарт.

и если она описанные вами проблемы не исправляет, то есть комплекс проблем, ради решения которых и создана, то это проблема компилятора, а не моя

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

так то проблемы компилятора.

Нет, то проблемы тех, кто не понимает, как работает препроцессор. И то, что «прагма once не включена в стандарт», тут не причём, — замени прагму на include guard и посмотри, что получится.

Так что всё-таки ты неосилятор.

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

Ну да. А вообще, для наглядности можно было бы показать результат clang++ -E/cpp на исходниках, а потом llvm-nm/nm на объектных файлах.

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

тут не причём, — замени прагму на include guard и посмотри, что получится.

а на хрена мне второй инклюд гуард? #pragma once вообще-то должна решать проблемы, которые инклюд гуард решить не может

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

вообще-то относится: всё это вместе относится к одной большой куче говна, которая называется «подключение библиотек в С++»

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

«Что вызывает ошибку линковки — в разных модулях (единицах трансляции) появляются глобальные переменные с одинаковыми именами.»

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

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

vertexua сам себе противоречит: Инопланетяне среди нас! (комментарий)

«По сути проблема ущербного использовани хедер-файлов»

но здесь уже можно и про код гуарды, и про прагмы поговорить

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

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

vertexua сам себе противоречит

Да нет же, просто ты ничего не понял. Вот ключевая фраза:

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

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

потому ее нужно определить в каком-то срр файле чтобы получить из него *.о файл у которого будет задекларирована вот такая вот переменная.

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

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

отрок подразумевает время жизни и размещение в (txt-сегменте?)

т.е видна own(тяжолое наследие Алгол60)переменая тока(ну если указателем на неё не сорить где ни попадя , ну и соблюдать гигиену доступа к памяти на которую С компилятору фиолетово) в месте обьявления (в функции) а живёт глобально.

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

а, собственно, с хрена ли?

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

Вот более подробное объяснение предыдущего примера:

λ desktop next-time → cat a.hh
struct Cookie {
    static int k;
};
int Cookie::k = 0;
λ desktop next-time → cat b.hh
int inc_b();
λ desktop next-time → cat c.hh 
int inc_c();
λ desktop next-time → cat b.cc
#include "a.hh"
#include "b.hh"
int inc_b() {
    return Cookie::k++;
}
λ desktop next-time → cat c.cc
#include "a.hh"
#include "c.hh"
int inc_c() {
    return Cookie::k++;
}
λ desktop next-time → cat main.cc
#include "b.hh"
#include "c.hh"
#include <iostream>
using namespace std;
int main() {
    cout << inc_b() << endl;
    cout << inc_c() << endl;
}
λ desktop next-time → 

Не линкует:

λ desktop next-time → export CXX=clang++
λ desktop next-time → make b.o
clang++    -c -o b.o b.cc
λ desktop next-time → make c.o
clang++    -c -o c.o c.cc
λ desktop next-time → make main.o
clang++    -c -o main.o main.cc
λ desktop next-time → clang++ *.o -o main
c.o:(.bss+0x0): multiple definition of `Cookie::k'
b.o:(.bss+0x0): first defined here
clang: error: linker command failed with exit code 1 (use -v to see invocation)
λ desktop next-time → 

Это происходит потому, что после препроцессорной обработки в каждой единице трансляции определяется Cookie::k:

λ desktop next-time → clang++ -E b.cc
# 1 "b.cc"
# 1 "b.cc" 1
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 138 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "b.cc" 2
# 1 "./a.hh" 1
struct Cookie {
    static int k;
};
int Cookie::k = 0;
# 2 "b.cc" 2
# 1 "./b.hh" 1
int inc_b();
# 3 "b.cc" 2
int inc_b() {
    return Cookie::k++;
}
λ desktop next-time → clang++ -E c.cc
# 1 "c.cc"
# 1 "c.cc" 1
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 138 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "c.cc" 2
# 1 "./a.hh" 1
struct Cookie {
    static int k;
};
int Cookie::k = 0;
# 2 "c.cc" 2
# 1 "./c.hh" 1
int inc_c();
# 3 "c.cc" 2
int inc_c() {
    return Cookie::k++;
}
λ desktop next-time → 

Можно посмотреть объектные файлы и убедиться, что в них объявляются одинаковые внешние символы, которые и приводят к ошибке линковки:

λ desktop next-time → llvm-nm b.o               
00000000 T _Z5inc_bv
00000000 B _ZN6Cookie1kE
λ desktop next-time → llvm-nm c.o
00000000 T _Z5inc_cv
00000000 B _ZN6Cookie1kE
λ desktop next-time → 

#pragma once и include guards спасают от другого (но похожего) класса ошибок, посмотри в Википедии.

Что же получится, если вынести определение Cookie::k из хедера:

λ desktop next-time → cat a.hh
struct Cookie {
    static int k;
};
λ desktop next-time → cat a.cc
#include "a.hh"
int Cookie::k = 0;
λ desktop next-time → make a.o
clang++    -c -o a.o a.cc
λ desktop next-time → make b.o
clang++    -c -o b.o b.cc
λ desktop next-time → make c.o
clang++    -c -o c.o c.cc
λ desktop next-time → make main.o
clang++    -c -o main.o main.cc
λ desktop next-time → clang++ *.o -o main
λ desktop next-time → ./main
0
1
λ desktop next-time → 

Смотрим объектники:

λ desktop next-time → llvm-nm a.o    
00000000 B _ZN6Cookie1kE
λ desktop next-time → llvm-nm b.o
00000000 T _Z5inc_bv
         U _ZN6Cookie1kE
λ desktop next-time → llvm-nm c.o
00000000 T _Z5inc_cv
         U _ZN6Cookie1kE
λ desktop next-time → 

Видно, что Cookie::k объявлена только в a.o, в остальных объектниках эта переменная не определена и будет резолвиться на стадии линковки.

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

Да, можно придумать решение этой проблемы. Но оно усложнит систему и поломает текущий интерфейс взаимодействия.

ien
()

что нелогичного-то?

Некторые тянки меня инопланетянином называли, да

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

ну как сказать: представьте, некому маляру начальник дал задание покрасить стену в жёлтый цвет и потом, говорит, как покрасишь, сразу перекрась в зелёный. маляр послал начальника в пешую прогулку. маляр лентяй или нет? а потом рассказал про этот случай другим малярам — он нытик?

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

это всё понятно. я спрашиваю, не как сделано, а почему именно так сделано.

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

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

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