LINUX.ORG.RU

Странный варнинг c++.

 ,


1

3

Есть вот такая структура, типы с py:: — из pybind11-неймспейса.

struct csection_set_cfg {
	typedef mprog_t::opc_t opcode;
	
	std::vector<std::string>      ptinfo, bginfo;
	float                         max_energy;
	std::vector<mprog_t>          progs;
	std::vector<float>            cffts, points, cstabs, rates, tabs;
	std::map<uint16_t, py::tuple> dset;
	uint8_t                       tsize, ntype; 
	uint8_t                       ncsect, nprog;
	
	csection_set_cfg
	(py::str, py::str, std::vector<py::dict>, float, py::dict);
};
Компилирую и вылезает такое:
./g++ -std=c++20 -fPIC -O3 -Wall -Wpedantic -fopenmp -Waggressive-loop-optimizations -c def_csections.cxx -I. -I./fmt/include 
In file included from def_csections.cxx:1:
def_csections.hxx:21:8: warning: ‘csection_set_cfg’ declared with greater visibility than the type of its field ‘csection_set_cfg::dset’ [-Wattributes]
   21 | struct csection_set_cfg {
      |        ^~~~~~~~~~~~~~~~
WAT. Причём на остальные члены класса ему пофиг, а на этот конкретный ругаетс хоть тресни. ЧЯДНТ?

★★★★★

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

Я бы смотрел на выхлоп препроцессора.

bugfixer ★★★★★
()

Класс поля объявлен с атрибутом __attribute__((visibility("hidden"))), твой класс по умолчанию имеет __attribute__((visibility("default"))).

https://pybind11.readthedocs.io/en/stable/faq.html#someclass-declared-with-greater-visibility-than-the-type-of-its-field-someclass-member-wattributes

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

Я её не открывал даже, и вообще вопрос не про питон а про с++. Не ожидал что типы вообще куда-то могут экспортироваться. Экспортироваться должны функции (и методы, кроме виртуальных) и глобальные переменные, остальное кроме отладчика никому не интересно.

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

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

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

Зачем классам области видимости? Это ж не контент.

Как минимум, уменьшается вероятность конфликтов при линковке с другтми библиотеками, наружу не торчат лишние, не публичные методы, уменьшается размер бинаря)

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

Методы это методы, = функции и они понятное дело экспортируются (и могут быть видимыми или нет). А вот зачем то же самое применять к самому классу? Он всё равно в каждой единице компиляции берётся из заголовочного файла и линкер вообще ничего с этой сущностью делать не может.

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

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

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

В одной версии это может быть

struct A {
  int b;
};

А в другой

struct A {
  int b;
  int c;
};

Для компановщика важен размер этой структуры. Если он видит функцию foo(A a), то какой размер он должен учесть? Для этого и нужно указать локальную линковку. Тогда компоновщик для каждой либы использует свою версию A. Как-то так «на пальцах»))

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

Компоновщику плевать какие там размеры, этим занимается компилятор. Компоновщик только подставит адрес функции foo() на нужное место. Даже прототипы функции он сравнивать не будет и не может, прекрасно слинкует функцию int foo(int a) { ... } из одного файла с объявленным и использованным extern void foo(void *p, char c); в другом, если только декорации, навешанные на имя функции с++ компилятором, не приведут к тому что у них разные декорированные имена. Более того, компоновщик может слинковать даже переменную char st[100] = "abc"; с вызовом прототипа void st(int a);. Единственное, что важно компоновщику - это имя символа, больше ничего. Проверку типов делать компилятор с помощью заголовочных файлов, которые должны совпадать (если не совпадают - сам виноват, получишь битый бинарник). Соответственно, типы аргументов он не смотрит и никак не обрабатывает.

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

Возможно, я слишком упростил пример). Давай рассмотрим посложнее:

struct A {
  int b;
  void foo() { cout << b; }
};
struct A {
  int b;
  int c;
  void foo() { cout << b << c; }
};

и есть пара вызовов A a; a.foo(). В одной либе и другой соответственно. Как, по твоему мнению, компоновщик должен узнать какую версию foo вызвать? Для компановщика и то и другое будет выглядеть как A::foo(A*) (опять же упрощенно).

прекрасно слинкует функцию int foo(int a) { ... } из одного файла с объявленным и использованным extern void foo(void *p, char c);

Звучит странно. В плюсах имена параметров являются частью name mangling. Иначе перегрузка бы не работала, от слова совсем. А в Си это по идее не скомпилиться, там нет перегрузки.

UP: С extern может скомпиляться, ок) Но сомневаюсь что будет работать sizeof(void *) + sizeof(char) != sizeof(int) как правило.

Компоновщику плевать какие там размеры

Я бы не был столь категоричным. В компановщиках давно используют всякие оптимизации типа LTO. И они давно работают сложнее чем просто матчинг имен.

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

и есть пара вызовов A a; a.foo(). В одной либе и другой соответственно. Как, по твоему мнению, компоновщик должен узнать какую версию foo вызвать? Для компановщика и то и другое будет выглядеть как A::foo(A*) (опять же упрощенно).

Если после декорирования (mangling) у этих двух методов окажутся одинаковые имена - то будет конфликт имён. Но в данном случае вроде имена получатся разные и всё норм. Но про тип класса компоновщику знать ничего не нужно.

Звучит странно. В плюсах имена параметров являются частью name mangling

Я ж там уточнил про с++-декорирование. Да, оно тут решит проблему, но, повторю, его делает компилятор. Как раз для того, чтоб компоновщик мог тупо сравнить строки имён и больше ничего не делать.

А в Си это по идее не скомпилиться, там нет перегрузки.

Речь не про перегрузку, а про случай когда в двух разных модулях используются разные прототипы одной и той же функции. Для избежания такой ситуации принято инключить общие .h файлы со всеми прототипами - тогда компилятор может везде проверить что они совпадают, но инклюд .h можно и не делать, либо инключить разные .h - тогда компилятор ничего сравнить не сможет, а линкер - в любом случае не умеет. Опять оговорюсь, с++-декорирование в большинстве случаев приводит к устранению таких ситуаций, но можно сделать extern «C» или специально придумать две функции, на самом деле разные, но у которых декорированные имена совпадут.

Но сомневаюсь что будет работать sizeof(void *) + sizeof(char) != sizeof(int) как правило.

Работать полученный бинарник конечно будет некорректно (обычный итог - сегфолт). Но на этапе компиляции эту проблему никто не заметит.

В компановщиках давно используют всякие оптимизации типа LTO

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

UPD всё оказалось хуже, декорированные имена методов совпадают и компоновщик выкинул второй foo() заменив его вызовом первого foo() для второго класса.

a.cpp

#include <iostream>

struct A {
  int c;
  void foo() { std::cout << "a::foo " << c << "\n"; }
};

int a1(void) {
  A a;
  a.c = 2;
  a.foo();
  return 10;
}
b.cpp
#include <iostream>

struct A {
  int b;
  int c;
  void foo() { std::cout << "b::foo " << b << " " << c << "\n"; }
  void foox() { std::cout << "b::foox " << b << " " << c << "\n"; }
};

int a2(void) {
  A a;
  a.b = 3;
  a.c = 4;
  a.foo();
  a.foox();
  return 11;
}
main.cpp
int a1();
int a2();
int main(void) {
  a1();
  a2();
  return 0;
}
$ g++ a.cpp b.cpp main.cpp
$ ./a.out 
a::foo 2
a::foo 3
b::foox 3 4

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

Если после декорирования (mangling) у этих двух методов окажутся одинаковые имена - то будет конфликт имён.

Символы запросто могут быть weak. Всё правильно написали - по сути дела visibility это полу-легальный (в смысле я не помню чтобы в стандарте хоть слово об этом было) способ обойти ODR. Я потому сразу и написал что на выхлоп препроцессора смотреть надо - там сразу было бы видно откуда символ пришёл, и с какими атрибутами.

UPD

всё оказалось хуже, декорированные имена методов совпадают и компоновщик выкинул второй foo() заменив его вызовом первого foo() для второго класса.

Вот он - «ODR в действии».

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

Могут быть да. Но речь не об этом а о том, что имя класса компоновщику не нужно. И кстати я сейчас проверил - имя класса нигде в .o файле и не фигурирует само по себе - только в качестве куска декорированных имён его методов. Так что всё ещё непонятно какая может быть область видимости у того, что и символом то не является нигде кроме текстового исходника.

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

UPD всё оказалось хуже, декорированные имена методов совпадают и компоновщик выкинул второй foo()

Если завернешь структуру в анонимный неймспейс, все должно заработать как надо ;)

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

только в качестве куска декорированных имён его методов

Ага так манглинг в плюсах и работает, еще неймспейсы добавляются и параметры

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

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

Я не вдавался в детали как именно mangle’ят RTTI in general, и vtable in particular, но там абсолютно точно имя класса тоже фигурировать будет.

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

Почему бы не забить?

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

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

Это всё никак не отвечает на вопрос, где у класса visibility.

Щя ты до этого сам дойдешь раз начал, ты только не ссы XDD

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

UP: Изначально без неймспейсов, у тебя возникло нарушение ODR. «Видимость» это правит. Анонимныйе неймспейсы убрали extern линковку и все заработало.

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

Видимость это понятие для компоновщика, а компоновщик вообще не знает что такое класс и не видит его имени. Твоё объяснение непонятно. Покажешь код?

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

Символ может зарезолвиться совсем не в то что Вы ожидаете, с довольно непредсказуемыми последствиями.

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

Покажешь код?

Я занят немного, может позже, если лень не будет)

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

Забыл один момент, это как правило используется для сошек/дллек для твоей статической линковки __attribute__ ((visibility ("hidden"))) хз как отработает.

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

Если конфликтов нет - то всё должно быть «ровно». Все эти пляски с visibility больше нужны авторам библиотек чтобы прятать «потроха» и иметь возможность upgrades не ломая клиентский код без пересборки.

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

То что это для библиотек я знаю, больше оно ни на что не влияет. hidden означает что символ из библиотеки не экспортируется. Что оно означает применительно к классу я так и не понял - он никогда не экспортируется.

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

Так у класса есть методы, которые могут экспортнуться. Это по сути просто функции. Могут быть статик мемберы. У последних даже свой адрес есть, может даже уникальный, здесь от параметров компиляции зависит. Он тоже может экспортнуться. Не писать же к каждому методу/мемберу атрибут.

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

Так у класса есть методы …

Почему в контексте «экспорта классов» все упорно забывают / игнорируют RTTI и vtable?

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

Почему в контексте «экспорта классов» все упорно забывают / игнорируют RTTI и vtable?

И это хорошее замечание xD. Как к ним иначе добавить атрибут я даже не представляю.

PRN
()
Последнее исправление: PRN (всего исправлений: 1)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.