簡體   English   中英

可重現:為什么在C中傳遞此對象會破壞我的代碼?

[英]Reproducible: Why does passing this object in C break my code?

根據我的理解,C假定所有參數都是int的,並且它返回int。 我想繞過這個對象,但我不知道如何和AFAIK一樣大小的int,但它壞了。 這是可復制的代碼。

在testc.c。 注意:此文件必須在C文件中。

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

在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中崩潰,我得到了此打印輸出,該輸出顯示傳遞的對象不正確。 我不明白“ B”到底從哪里來。 這發生在調試模式下。 發布因訪問沖突而崩潰。

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

如果您好奇的話,如果您注釋掉構造函數(並且不進行其他更改),它將在gcc中起作用。 如果將類更改為struct,那么沒有成員是私有的,則它將在msvc2010中工作。 這個修復是胡說八道,但是當我這樣做並神奇地使代碼工作時,它似乎考慮了POD。 這很奇怪,因為C中的定義沒有更改(因為沒有定義)。 構造函數沒有做任何不同的事情。

根據我的理解,C假定所有參數都是int的,並且它返回int。

不完全的。

在1999 ISO C標准之前,調用沒有可見聲明的函數將導致編譯器假定其返回類型為int的結果。 這不適用於參數; 假設它們是參數的(提升的)類型(如果有)。

C99放棄了“隱式int”規則; 調用沒有可見聲明的函數是違反約束的,這基本上意味着它是非法的。 即使在1999年前的C語言中,這也不是一個好主意。

如果要從C調用C ++,則調用的任何函數都應具有與兩種語言都兼容的參數和返回類型。 C沒有類或模板,因此讓C程序調用返回WrappedPointer<MyType>的C ++函數至少是有問題的。

假設指針的大小與int相同,則使代碼極不可移植。 即使它們大小相同,也不能互換。 int和指針函數結果可能使用不同的機制(例如,不同的CPU寄存器)返回。

我建議讓test_c1()test_c2()返回void* ,它可以指向任何類型的對象。 並且您的C源代碼需要針對其調用的任何函數具有可見的聲明(最好是原型)。

函數test_c2()在堆棧上創建ObjPtr s ,然后將其返回。 但是當test_c2()返回時, s超出范圍(並且其內存已釋放)。

如果您在此處閱讀,您會發現平台和編譯器之間的調用約定有很大不同,並且差異可能非常細微: http : //www.angelcode.com/dev/callconv/callconv.html

我認為這里發生的事情是,使用的調用約定將始終在寄存器中返回ints,但是有更嚴格的標准來確定對象是否可以在寄存器中返回,正如您在更改類時注意到的那樣。 當編譯器確定對象不符合這些條件時,它將決定應將對象返回到內存中,而不是通過寄存器返回。 這導致災難。 C ++代碼最終試圖從堆棧中讀取比C語言實際推送的參數更多的參數,並且錯誤地將堆棧的一部分解釋為指向內存的指針,認為它可以將ObjPtr寫入其中。 根據編譯器和平台的不同,您可能會立即變得“幸運”並崩潰,或者“不幸”並在意外的地方編寫ObjPtr,然后崩潰或發生一些奇怪的事情。

我不推薦這樣做,但是如果您確保C ++中的函數聲明與C匹配(即它們使用整數),則可能最有可能使此工作生效。 然后在C ++中執行您需要的任何reinterpret_casts,交叉手指並祈禱。 至少以這種方式,您知道不會因呼叫簽名不匹配而被絆倒,而這正是您現在遇到的問題。


好的,所以您想知道為什么調用約定將非POD類型區別對待。 考慮復制構造函數的存在意味着什么。 每當您按值返回ObjPtr時,都必須使用對復制對象的引用以及目標對象的this指針來調用該復制構造函數。 做到這一點的唯一方法是,如果調用方已將隱藏指針傳遞到它已分配的用於存儲返回值的內存中。 編譯器沒有在copy-constructor內部查看,看它沒有做任何花哨的事情。 它只是看到您已經定義了一個,並且注意到該類很簡單,並且永遠不會在寄存器中返回。

有關聚合和POD的詳細信息,請參見此處。 我特別喜歡有關C ++ 11中發生了什么變化的答案。 什么是聚合和POD,它們為何/為什么特別?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM