Поиск по неквалифицированному имени на C : разный размер структуры в разных cpp приводит к тому, что operator new выделяет меньше памяти, чем процессы конструктора?

#c #sizeof #one-definition-rule #unqualified-name

#c #sizeof #one-definition-rule #неквалифицированное имя

Вопрос:

Вот пример:

Main.cpp :

 #include "MooFoobar.h"
#include "MooTestFoobar.h"

#include "FoobarUser.h"

namespace moo::test::xxx {
    struct X
    {
        void* operator new(const size_t size);

        FoobarUser m_User;
    };

    void* X::operator new(const size_t size)
    {
        printf("Allocated size: %zdn", size);
        return malloc(size);
    }
} // namespace moo::test::xxx

int main()
{
    new moo::test::xxx::X;
    printf("Actual size: %zd, member size: %zdn", sizeof(moo::test::xxx::X), sizeof(moo::test::xxx::FoobarUser));
    return 0;
}
  

MooFoobar.h :

 namespace moo {
    struct Foobar
    {
        char m_Foo[64];
    };
} // namespace moo
  

MooTestFoobar.h :

 namespace moo::test {
    struct Foobar
    {
        char m_Foo[32];
    };
} // namespace moo::test
  

FoobarUser.h :

 #include "MooFoobar.h"

namespace moo::test::xxx {
    struct FoobarUser
    {
        FoobarUser();
        ~FoobarUser();

        Foobar m_Foobar;
    };
} // namespace moo::test::xxx
  

FoobarUser.cpp :

 #include "FoobarUser.h"
#include <cstdio>

moo::test::xxx::FoobarUser::FoobarUser()
    : m_Foobar()
{
    printf("FoobarUser constructor, size: %zdn", sizeof(*this));
}

moo::test::xxx::FoobarUser::~FoobarUser()
{}
  

Итак, что здесь происходит: в зависимости от порядка включения неквалифицированное имя разрешается в разных типах, и в FoobarUser.cpp мы получаем size 64 , в Main.cpp мы получаем size 32 . Не только sizeof отличается — operator new вызывается с неправильным ( 32 ) размером, но конструктор инициализирует размер 64 , который приводит к повреждению памяти.

Как в clang, так и в msvc результатом этой программы является:

 Allocated size: 32
FoobarUser constructor, size: 64
Actual size: 32, member size: 32
  

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

Но я не могу найти ни одного пункта в C std, который бы указывал на какой-либо из этих недопустимых / неправильно сформированных кодов. Кто-нибудь может мне помочь?

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

Комментарии:

1. Я не понимаю, почему это вас удивляет. Когда вы вызываете свой компилятор для компиляции Main.cpp , через включения он получает определение Foobar , которое отличается от того, которое видит FoobarUser.cpp , когда вы вызываете свой компилятор для компиляции этой последней единицы перевода. И да, это, очевидно, проблема.

2. Я имел в виду «найти с помощью неквалифицированного поиска» под «увиденным» или «получает». Мой плохой.

3. Да, я понимаю, что это «логично», за исключением того, что это приводит к некорректному коду и нет предупреждения или диагностики, которые скажут вам «размер отличается, вы собираетесь прострелить себе ногу». Плюс std обычно говорит что-то вроде «если вы делаете X, программа неправильно сформирована, диагностика не требуется»

4. нарушение ODR @AndrianNord не совсем поддается проверке реализациями. Потребовалась бы действительно дорогая и сложная программа для обнаружения большинства из них, но не всех

Ответ №1:

Чтобы строго ответить на ваш вопрос

Я не могу найти ни одного пункта в C std, который бы указывал на какой-либо из этих недопустимых / неправильно сформированных кодов. Кто-нибудь может мне помочь?

Это [basic.def.odr]/12.2

В программе может быть более одного определения типа класса […] при условии, что каждое определение отображается в разных единицах преобразования, и при условии, что определения удовлетворяют следующим требованиям. Учитывая такой объект с именем D, определенный более чем в одной единице перевода, тогда

  • каждое определение D должно состоять из одной и той же последовательности токенов; и
  • в каждом определении D соответствующие имена, просматриваемые в соответствии с [basic.lookup], должны ссылаться на объект, определенный в определении D, или должны ссылаться на тот же объект, после разрешения перегрузки и после сопоставления частичной специализации шаблона ([temp.over]), за исключением того, что имя может ссылаться на

    • [… ничего существенного]

В вашей программе FoobarUser определено в обеих ваших единицах перевода, но имя Foobar внутри ссылается на — согласно правилу неквалифицированного поиска — две разные сущности ( moo::test::FooBar и moo:FooBar ). И это нарушает правило одного определения. Диагностика не требуется.

Комментарии:

1. Да, у меня было подозрение, что это ODR, но я ожидал, что «это нарушение ODR». Мне не показалось, что нужно искать определение odr. Спасибо