Воспроизводимый: почему передача этого объекта в C нарушает мой код? - PullRequest
0 голосов
/ 09 марта 2012

Насколько я понимаю, C предполагает, что все параметры являются целыми и возвращает целые числа. Я хотел бы обойти этот объект, но я понятия не имею, как и AFAIK его же размер INT, но он ломается. Вот воспроизводимый код.

В testc.c. Примечание: это ДОЛЖНО быть в файле C.

int test_c1() {
    return test_c3(test_c2());
}

In testcpp.cpp

#include <iostream>
using namespace std;
struct MyType{
    int a, b;
};

template <class T>
struct WrappedPointer {
    T* lhs;
public:
    void LHS(T*v) { lhs=v; }
    T*   LHS() { return lhs; }
    WrappedPointer(){}
    WrappedPointer(T*value) : lhs(value){}
    WrappedPointer(const WrappedPointer&v) : lhs(v.lhs){}
    T* operator->() const { return lhs; }
    T* operator*() const { return lhs; }
};
typedef WrappedPointer<MyType> ObjPtr;
static_assert(sizeof(ObjPtr) == sizeof(int), "");
static_assert(sizeof(ObjPtr) == sizeof(void*),"");

extern "C" {
    ObjPtr test_c1();
    ObjPtr test_c2() {
        //ObjPtr s=0;
        ObjPtr s;
        s.LHS(0);
        cout <<"c2 " << s.LHS() << endl;
        return s; 
    }
    ObjPtr test_c3(ObjPtr v) { 
        cout <<"c3 " << v.LHS() << endl;
        return v; 
    }
};

int main() {
    auto v = test_c1();
    cout <<"main " << v.LHS() << endl;
}

флаги компиляции gcc

gcc -Wall -c testc.c
testc.c: In function 'test_c1':
testc.c:2:2: warning: implicit declaration of function 'test_c3' [-Wimplicit-function-declaration]
testc.c:2:2: warning: implicit declaration of function 'test_c2' [-Wimplicit-function-declaration]
g++ -std=c++0x -Wall -c testcpp.cpp
g++ testc.o testcpp.o
a.exe

Должен произойти сбой, и, как вы можете видеть, единственным предупреждением, которое я когда-либо получал, была неявная функция :(. Почему происходит сбой? Особенно, когда я утверждал, что ObjPtr действительно того же размера, что и int. Как исправить это так, что я могу обойти ObjPtr? Я не могу изменить библиотеку C, так что testc.c отключен.

-edit- вместо сбоя в VS 2010 я получаю эту распечатку, которая показывает, что переданный объект неверен. Я не понимаю, откуда взялась буква «В». Это происходит в режиме отладки. Релиз сбой с нарушением прав доступа.

c2 00000000
c3 0046F8B0
main CCCCCCCC
B
Press any key to continue . . .

Если вам интересно, если вы закомментируете конструкторы (и больше ничего не меняете), это будет работать в gcc. Если вы измените класс на struct, чтобы ни один член не был закрытым, он будет работать в msvc2010. Это исправление бессмысленно, но, похоже, его POD учитывается, когда я делаю это, и волшебным образом код работает. Что странно, поскольку определение в C не изменилось (так как определения нет). И конструкторы не делают ничего другого.

Ответы [ 3 ]

3 голосов
/ 09 марта 2012

Насколько я понимаю, C предполагает, что все параметры являются целыми, и возвращает целые числа.

Не совсем.

До стандарта ISO C 1999 года вызов функции без видимого объявления заставил бы компилятор предположить, что он возвращает результат типа int. Это не относится к параметрам; предполагается, что они имеют (повышенный) тип (ы) аргумента (ов), если таковые имеются.

C99 отбросил правило "implicit int"; вызов функции без видимого объявления является нарушением ограничения, что в основном означает, что это незаконно. Это не очень хорошая идея даже до 1999 года.

Если вы собираетесь вызывать C ++ из C, все вызываемые вами функции должны иметь параметры и возвращаемые типы, совместимые с обоими языками. C не имеет классов или шаблонов, поэтому наличие программы на C, вызывающей функцию C ++, которая возвращает WrappedPointer<MyType>, по меньшей мере, сомнительно.

Предполагая, что указатели имеют тот же размер, что и int, делает ваш код крайне непереносимым. Даже если они одного размера, они не взаимозаменяемы; int и результаты функции указателя могут быть возвращены с использованием разных механизмов (например, разных регистров ЦП).

Я предлагаю иметь test_c1() и test_c2() return void*, которые могут указывать на объект любого типа. И ваш источник C должен иметь видимые объявления (предпочтительно прототипы) для любых функций, которые он вызывает.

0 голосов
/ 10 марта 2012

Если вы прочитаете здесь, вы увидите, что соглашения о вызовах значительно различаются между платформами и компиляторами, и различия могут быть незначительными: http://www.angelcode.com/dev/callconv/callconv.html

Я думаю, что здесь происходит то, что используемое соглашение о вызовах будетвсегда возвращайте ints в регистрах, но у него есть более строгие критерии, чтобы определить, можно ли вернуть объект в регистр, как вы заметили, когда вносили изменения в класс.Когда компилятор решает, что объект не соответствует этим критериям, он решает, что объект должен быть возвращен в память, а не через регистр.Это приводит к катастрофе.Код C ++ в конечном итоге пытается прочитать больше аргументов из стека, чем фактически выдавил код C, и неверно интерпретирует часть стека как указатель на память, в которую он может записать ObjPtr.В зависимости от компилятора и платформы вы можете «повезти» и сразу же потерпеть крах, или «не повезло», и написать ObjPtr где-нибудь неожиданно, а затем вылететь позже или сделать что-то странное.

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


Хорошо, поэтому вы хотите знать, почему обрабатываются не-POD типыиначе соглашением о вызовах.Подумайте, что означает наличие копирующего конструктора.Всякий раз, когда вы возвращаете ObjPtr по значению, этот конструктор копирования должен вызываться со ссылкой на скопированный объект и указателем this для целевого объекта.Единственный способ сделать это - это если вызывающий передал скрытый указатель на память, которую он уже выделил для хранения возвращаемого значения.Компилятор не заглядывает внутрь конструктора копирования, чтобы увидеть, что он не делает ничего особенного.Он просто видит, что вы его определили, и отмечает, что класс нетривиален и никогда не будет возвращен в регистр.

См. Также здесь для получения подробной информации о агрегатах и ​​POD.Мне особенно нравится ответ о том, что изменилось в C ++ 11. Что такое агрегаты и POD и как / почему они особенные?

0 голосов
/ 09 марта 2012

Функция test_c2() создает ObjPtr s в стеке и затем возвращает его.Но s выходит из области видимости (и его память освобождается), когда возвращается test_c2().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...