2010-06-05 20 views
15

मैं अपने कोड के लिए यूनिट परीक्षण को कार्यान्वित करने की कोशिश कर रहा हूं और मुझे इसे करने में कठिनाई हो रही है।सी ++ - क्या यूनिट परीक्षण में मेमोरी लीक परीक्षण को कार्यान्वित करना संभव है?

आदर्श रूप से मैं न केवल अच्छी कार्यक्षमता के लिए बल्कि उचित मेमोरी आवंटन/डीलोकेशन के लिए कुछ कक्षाओं का परीक्षण करना चाहता हूं। मुझे आश्चर्य है कि यह जांच यूनिट परीक्षण ढांचे का उपयोग करके किया जा सकता है। मैं Visual Assert बीटीडब्ल्यू का उपयोग कर रहा हूं। यदि संभव हो, तो मुझे कुछ नमूना कोड देखना अच्छा लगेगा!

उत्तर

13

लीक जांच करने के लिए आप डीवी स्टूडियो में डीबग कार्यक्षमता का उपयोग कर सकते हैं - जब तक कि आपका यूनिट परीक्षण डीबग सी-रनटाइम का उपयोग करके चलाए।

एक साधारण उदाहरण कुछ इस तरह दिखेगा:

#include <crtdbg.h> 
struct CrtCheckMemory 
{ 
    _CrtMemState state1; 
    _CrtMemState state2; 
    _CrtMemState state3; 
    CrtCheckMemory() 
    { 
    _CrtMemCheckpoint(&state1); 
    } 
    ~CrtCheckMemory() 
    { 
    _CrtMemCheckpoint(&state2); 
    // using google test you can just do this. 
    EXPECT_EQ(0,_CrtMemDifference(&state3, &state1, &state2)); 
    // else just do this to dump the leaked blocks to stdout. 
    if(_CrtMemDifference(&state3, &state1, &state2)) 
     _CrtMemDumpStatistics(&state3); 
    } 
}; 

और एक इकाई परीक्षण में इसका उपयोग करना:

UNIT_TEST(blah) 
{ 
    CrtCheckMemory check; 

    // TODO: add the unit test here 

} 

कुछ इकाई परीक्षण चौखटे अपने स्वयं के आवंटन बनाने - गूगल के उदाहरण ब्लॉक आवंटित के लिए जब एक यूनिट परीक्षण विफल रहता है, तो किसी भी अन्य परीक्षण के लिए विफल होने वाले किसी भी परीक्षण ब्लॉक में हमेशा झूठी सकारात्मक "रिसाव" होती है।

+2

यह एक बहुत ही "साफ" समाधान है। मैंने कोशिश की (एक स्वीकार्य रूप से सरल मामले में) और यह उम्मीद के रूप में काम किया। +1 – sevaxx

+1

मैं दूसरा करूंगा। यह वास्तव में एक साफ समाधान है।बस _CrtDumpMemoryLeaks फ़ंक्शन को कॉल करना Google परीक्षण के साथ काम नहीं करता है क्योंकि यह गलती से ढांचे में कुछ रिसाव की रिपोर्ट करता है, लेकिन यह समाधान उस समस्या से बचाता है। हालांकि आपको प्रत्येक टेस्ट केस के शीर्ष पर कक्षा का उदाहरण बनाने के लिए याद रखना होगा। – tathagata

+2

मैंने सफलतापूर्वक इस समाधान को सैकड़ों परीक्षणों के एक बड़े सेट में जोड़ा। आप एक क्लाइंट वैरिएबल के रूप में एक पॉइंटर जोड़ सकते हैं और इसे TEST_METHOD_INITIALIZE में आवंटित कर सकते हैं और इसे TEST_METHOD_CLEANUP में हटा सकते हैं। इस तरह यह प्रति बार TEST_CLASS – SecsAndCyber

1

आप आवंटन पर स्मृति ट्रैकिंग सूचनाओं को जोड़कर, नए, हटाए जाने, मॉलोक और मुफ्त कार्यों के अपने कार्यान्वयन प्रदान करके परीक्षणों पर स्मृति रिसाव का पता लगाने में सक्षम हो सकते हैं।

+1

यह ऐसा कुछ है जिसे मैं टालना चाहता हूं ... – sevaxx

+1

ठीक है, आपको आवंटन को ट्रैक करने वाले आवंटन का वैकल्पिक कार्यान्वयन करना होगा। इसका मतलब यह नहीं है कि आपको इसे खुद लिखना है ... –

+0

यह सही है। आवंटक libs के लिए ऑनलाइन देखो। नेडलोक एक है जिसे मैं जानता हूं (ओग्रे इसका उपयोग करें)। यदि आप ऐसा कुछ नहीं करना चाहते हैं, तो आप इसके बजाय बाहरी उपकरण का उपयोग कर सकते हैं ... लेकिन यह आपके यूनिट परीक्षणों में उपयोग योग्य नहीं होगा। विजुअल स्टूडियो में एक मूल मेमोरी लीक चेकर होता है लेकिन यह पता लगाने के लिए केवल इतना अच्छा होता है कि स्मृति मेले के बारे में हमेशा जानकारी प्रदान नहीं करता है ... – Klaim

5

आप Google की tcmalloc आवंटन लाइब्रेरी का उपयोग कर सकते हैं, जो heapchecker प्रदान करता है।

(ध्यान दें कि heapchecking अपने कार्यक्रम के प्रदर्शन के लिए ध्यान देने योग्य भूमि के ऊपर जोड़ सकते हैं, ताकि आप शायद केवल डिबग बनाता है या इकाई परीक्षण पर सक्षम करना चाहते हैं।)

और तुम उदाहरण कोड मांगा जाए, तो here it is

+0

टीसीएमएलओसी के लिए एक उपलब्ध लिंक: http: //goog-perftools.sourceforge। नेट/डॉक्टर/tcmalloc.html –

1

1) मेरी कुछ जांच के बाद और क्रिस बेके के बहुत अच्छे समाधान (विंडोज़ के लिए) के आधार पर, मैंने लिनक्स ओएस के लिए एक बहुत ही समान समाधान किया है।

2) मेरे स्मृति रिसाव का पता लगाने के लक्ष्यों:

बहुत स्पष्ट हैं - रिसाव का पता लगाने के साथ-साथ, जबकि:

2.1) एक सटीक ढंग से आदर्श रूप में - से संकेत मिलता है कि उनकी संख्या कितनी बाइट्स अभी तक आबंटित किया गया पुनः आवंटित की जाती नहीं।

2.2) सर्वोत्तम प्रयास - यदि बिल्कुल नहीं, तो "झूठी सकारात्मक" तरीके से संकेत मिलता है (हमें एक रिसाव के बारे में बताएं, भले ही यह जरूरी नहीं है और साथ ही साथ किसी भी रिसाव का पता लगाना न भूलें)। यहां अपने आप पर अधिक कठोर होना बेहतर है।

2.3) चूंकि मैं जीटीएस्ट फ्रेमवर्क में अपने यूनिट परीक्षण लिख रहा हूं - प्रत्येक जीटीएस्ट इकाई परीक्षण को "परमाणु इकाई" के रूप में जांचें।

2.4) मॉलोक/फ्री का उपयोग करके "सी-स्टाइल" आवंटन (विध्वंस) भी ध्यान में रखें।

2.5) आदर्श रूप से - सी ++ "स्थान आवंटन" पर विचार करें।

2.6) मौजूदा कोड में उपयोग और एकीकृत करने के लिए आसान (यूनिट परीक्षण के लिए जीटीएस्ट आधारित कक्षाएं)।

2।7) प्रत्येक परीक्षण और/या पूरे टेस्ट क्लास के लिए मुख्य चेक सेटिंग्स को "कॉन्फ़िगर" करने की क्षमता (स्मृति जांच सक्षम/अक्षम करें ...) की क्षमता है।

3) समाधान वास्तुकला:

मेरे समाधान GTest ढांचे का उपयोग करने का विरासत में मिला क्षमताओं का उपयोग करता है, इसलिए यह प्रत्येक इकाई परीक्षण वर्ग हम भविष्य में जोड़ देगा के लिए एक "आधार" वर्ग परिभाषित करता है। असल में, बेस क्लास मुख्य कार्यक्षमताओं को निम्नलिखित में विभाजित किया जा सकता है:

3.1) परीक्षण विफलता के मामले में ढेर पर आवंटित "अतिरिक्त स्मृति" की मात्रा को समझने के लिए "पहला" जीटीएस्ट स्टाइल परीक्षण चलाएं जैसा कि क्रिस बेके ने उपरोक्त उत्तर के अंतिम वाक्य में उल्लेख किया था।

3.2) एकीकृत करने में आसान - बस इस बेस क्लास से प्राप्त होता है और आपके यूनिट परीक्षण "TEST_F शैली" फ़ंक्शन लिखता है।

3.3.1) प्रत्येक परीक्षा के लिए, हम तय कर सकते हैं अन्यथा न कहा गया है कि क्या स्मृति रिसाव की जांच करते हैं या नहीं.यह SetIgnoreMemoryLeakCheckForThisTest() metohd के माध्यम से किया जाता है। नोट: इसे फिर से "रीसेट" करने की आवश्यकता नहीं है - यह जीटीएस्ट यूनिट परीक्षणों के तरीके के कारण स्वचालित रूप से अगले परीक्षण के लिए होगा (वे प्रत्येक फ़ंक्शन कॉल के लिए पहले सीटीओआर को कॉल करते हैं)।

3.3.2) इसके अलावा, किसी कारण से आप पहले से पता चलता है आपके परीक्षण "याद आती है" स्मृति के कुछ deallocations जाएगा और आप राशि पता है - आप में इस बात को ले जाने में दो कार्यों का लाभ ले सकते एक बार मेमोरी चेक करने पर विचार (जो, वैसे, परीक्षण के अंत में उपयोग की जाने वाली स्मृति की मात्रा से परीक्षण की शुरुआत में उपयोग में स्मृति की मात्रा को घटाकर "बस" द्वारा किया जाता है)।

// memoryLeakDetector.h: 
#include "gtest/gtest.h" 
extern int g_numOfExtraBytesAllocatedByGtestUponTestFailure; 

// The fixture for testing class Foo. 
class MemoryLeakDetectorBase : public ::testing::Test 
{ 
// methods: 
// ------- 
public: 
    void SetIgnoreMemoryLeakCheckForThisTest() { m_ignoreMemoryLeakCheckForThisTest= true; } 
    void SetIsFirstCheckRun() { m_isFirstTestRun = true; } 

protected: 

    // You can do set-up work for each test here. 
    MemoryLeakDetectorBase(); 

    // You can do clean-up work that doesn't throw exceptions here. 
    virtual ~MemoryLeakDetectorBase(); 

    // If the constructor and destructor are not enough for setting up 
    // and cleaning up each test, you can define the following methods: 

    // Code here will be called immediately after the constructor (right 
    // before each test). 
    virtual void SetUp(); 

    // Code here will be called immediately after each test (right 
    // before the destructor). 
    virtual void TearDown(); 

private: 
    void getSmartDiff(int naiveDiff); 
    // Add the extra memory check logic according to our 
    // settings for each test (this method is invoked right 
    // after the Dtor). 
    virtual void PerformMemoryCheckLogic(); 

// members: 
// ------- 
private: 
    bool m_ignoreMemoryLeakCheckForThisTest; 
    bool m_isFirstTestRun; 
    bool m_getSmartDiff; 
    size_t m_numOfBytesNotToConsiderAsMemoryLeakForThisTest; 
    int m_firstCheck; 
    int m_secondCheck; 
}; 

यहाँ और इस आधार वर्ग का स्रोत है:

नीचे हैडर आधार वर्ग है

// memoryLeakDetectorBase.cpp 
#include <iostream> 
#include <malloc.h> 

#include "memoryLeakDetectorBase.h" 

int g_numOfExtraBytesAllocatedByGtestUponTestFailure = 0; 

static int display_mallinfo_and_return_uordblks() 
{ 
    struct mallinfo mi; 

    mi = mallinfo(); 
    std::cout << "========================================" << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << "Total non-mmapped bytes (arena):" << mi.arena << std::endl; 
    std::cout << "# of free chunks (ordblks):" << mi.ordblks << std::endl; 
    std::cout << "# of free fastbin blocks (smblks):" << mi.smblks << std::endl; 
    std::cout << "# of mapped regions (hblks):" << mi.hblks << std::endl; 
    std::cout << "Bytes in mapped regions (hblkhd):"<< mi.hblkhd << std::endl; 
    std::cout << "Max. total allocated space (usmblks):"<< mi.usmblks << std::endl; 
    std::cout << "Free bytes held in fastbins (fsmblks):"<< mi.fsmblks << std::endl; 
    std::cout << "Total allocated space (uordblks):"<< mi.uordblks << std::endl; 
    std::cout << "Total free space (fordblks):"<< mi.fordblks << std::endl; 
    std::cout << "Topmost releasable block (keepcost):" << mi.keepcost << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << std::endl; 
    std::cout << std::endl; 

    return mi.uordblks; 
} 

MemoryLeakDetectorBase::MemoryLeakDetectorBase() 
    : m_ignoreMemoryLeakCheckForThisTest(false) 
    , m_isFirstTestRun(false) 
    , m_getSmartDiff(false) 
    , m_numOfBytesNotToConsiderAsMemoryLeakForThisTest(0) 
{ 
    std::cout << "MemoryLeakDetectorBase::MemoryLeakDetectorBase" << std::endl; 
    m_firstCheck = display_mallinfo_and_return_uordblks(); 
} 

MemoryLeakDetectorBase::~MemoryLeakDetectorBase() 
{ 
    std::cout << "MemoryLeakDetectorBase::~MemoryLeakDetectorBase" << std::endl; 
    m_secondCheck = display_mallinfo_and_return_uordblks(); 
    PerformMemoryCheckLogic(); 
} 

void MemoryLeakDetectorBase::PerformMemoryCheckLogic() 
{ 
    if (m_isFirstTestRun) { 
     std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - after the first test" << std::endl; 
     int diff = m_secondCheck - m_firstCheck; 
     if (diff > 0) { 
      std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - setting g_numOfExtraBytesAllocatedByGtestUponTestFailure to:" << diff << std::endl; 
      g_numOfExtraBytesAllocatedByGtestUponTestFailure = diff; 
     } 
     return; 
    } 

    if (m_ignoreMemoryLeakCheckForThisTest) { 
     return; 
    } 
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic" << std::endl; 

    int naiveDiff = m_secondCheck - m_firstCheck; 

    // in case you wish for "more accurate" difference calculation call this method 
    if (m_getSmartDiff) { 
     getSmartDiff(naiveDiff); 
    } 

    EXPECT_EQ(m_firstCheck,m_secondCheck); 
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - the difference is:" << naiveDiff << std::endl; 
} 

void MemoryLeakDetectorBase::getSmartDiff(int naiveDiff) 
{ 
    // according to some invastigations and assumemptions, it seems like once there is at least one 
    // allocation which is not handled - GTest allocates 32 bytes on the heap, so in case the difference 
    // prior for any further substrcutions is less than 32 - we will assume that the test does not need to 
    // go over memory leak check... 
    std::cout << "MemoryLeakDetectorBase::getMoreAccurateAmountOfBytesToSubstructFromSecondMemoryCheck - start" << std::endl; 
    if (naiveDiff <= 32) { 
     std::cout << "MemoryLeakDetectorBase::getSmartDiff - the naive diff <= 32 - ignoring..." << std::endl; 
     return; 
    } 

    size_t numOfBytesToReduceFromTheSecondMemoryCheck = m_numOfBytesNotToConsiderAsMemoryLeakForThisTest + g_numOfExtraBytesAllocatedByGtestUponTestFailure; 
    m_secondCheck -= numOfBytesToReduceFromTheSecondMemoryCheck; 
    std::cout << "MemoryLeakDetectorBase::getSmartDiff - substructing " << numOfBytesToReduceFromTheSecondMemoryCheck << std::endl; 
} 

void MemoryLeakDetectorBase::SetUp() 
{ 
    std::cout << "MemoryLeakDetectorBase::SetUp" << std::endl; 
} 

void MemoryLeakDetectorBase::TearDown() 
{ 
    std::cout << "MemoryLeakDetectorBase::TearDown" << std::endl; 
} 

// The actual test of this module: 


TEST_F(MemoryLeakDetectorBase, getNumOfExtraBytesGTestAllocatesUponTestFailureTest) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - START" << std::endl; 

    // Allocate some bytes on the heap and DO NOT delete them so we can find out the amount 
    // of extra bytes GTest framework allocates upon a failure of a test. 
    // This way, upon our legit test failure, we will be able to determine of many bytes were NOT 
    // deleted EXACTLY by our test. 

    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - size of char:" << sizeof(char) << std::endl; 
    char* pChar = new char('g'); 
    SetIsFirstCheckRun(); 
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - END" << std::endl; 
} 

अंत में, एक नमूना "GTest आधारित" इकाई परीक्षण वर्ग इस का उपयोग करता है बेस क्लास और मिस्ड डी-आवंटन का पता लगाने में सक्षम (या नहीं) सभी प्रकार के विभिन्न आवंटन और सत्यापन के लिए उपयोग और कई अलग-अलग पीओसी (अवधारणा का सबूत) दिखाता है।

// memoryLeakDetectorPocTest.cpp 
#include "memoryLeakDetectorPocTest.h" 
#include <cstdlib> // for malloc 

class MyObject 
{ 

public: 
    MyObject(int a, int b) : m_a(a), m_b(b) { std::cout << "MyObject::MyObject" << std::endl; } 
    ~MyObject() { std::cout << "MyObject::~MyObject" << std::endl; } 
private: 
    int m_a; 
    int m_b; 
}; 

MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest" << std::endl; 
} 

MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest" << std::endl; 
} 

void MemoryLeakDetectorPocTest::SetUp() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::SetUp" << std::endl; 
} 

void MemoryLeakDetectorPocTest::TearDown() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::TearDown" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeType) 
{ 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - START" << std::endl; 

    // allocate some bytes on the heap and intentially DONT release them... 
    const size_t numOfCharsOnHeap = 23; 
    std::cout << "size of char is:" << sizeof(char) << " bytes" << std::endl; 
    std::cout << "allocating " << sizeof(char) * numOfCharsOnHeap << " bytes on the heap using new []" << std::endl; 
    char* arr = new char[numOfCharsOnHeap]; 

    // DO NOT delete it on purpose... 
    //delete [] arr; 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - START" << std::endl; 

    std::cout << "size of MyObject is:" << sizeof(MyObject) << " bytes" << std::endl; 
    std::cout << "allocating MyObject on the heap using new" << std::endl; 
    MyObject* myObj1 = new MyObject(12, 17); 

    delete myObj1; 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyMallocAllocationForNativeType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - START" << std::endl; 
    size_t numOfDoublesOnTheHeap = 3; 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - sizeof double is " << sizeof(double) << std::endl; 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - allocaitng " << sizeof(double) * numOfDoublesOnTheHeap << " bytes on the heap" << std::endl; 
    double* arr = static_cast<double*>(malloc(sizeof(double) * numOfDoublesOnTheHeap)); 

    // NOT free-ing them on purpose !! 
    // free(arr); 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeSTLVectorType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - START" << std::endl; 
    std::vector<int> vecInt; 
    vecInt.push_back(12); 
    vecInt.push_back(15); 
    vecInt.push_back(17); 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedSTLVectorType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - START" << std::endl; 
    std::vector<MyObject*> vecMyObj; 
    vecMyObj.push_back(new MyObject(7,8)); 
    vecMyObj.push_back(new MyObject(9,10)); 

    size_t vecSize = vecMyObj.size(); 
    for (int i = 0; i < vecSize; ++i) { 
     delete vecMyObj[i]; 
    } 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationAndDeAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - START" << std::endl; 
    void* p1 = malloc(sizeof(MyObject)); 
    MyObject *p2 = new (p1) MyObject(12,13); 

    p2->~MyObject(); 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - START" << std::endl; 
    void* p1 = malloc(sizeof(MyObject)); 
    MyObject *p2 = new (p1) MyObject(12,13); 

    // Dont delete the object on purpose !! 
    //p2->~MyObject(); 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - END" << std::endl; 
} 

इस वर्ग के हेडर फाइल:

// memoryLeakDetectorPocTest.h 
#include "gtest/gtest.h" 
#include "memoryLeakDetectorBase.h" 

// The fixture for testing class Foo. 
class MemoryLeakDetectorPocTest : public MemoryLeakDetectorBase 
{ 
protected: 

    // You can do set-up work for each test here. 
    MemoryLeakDetectorPocTest(); 

    // You can do clean-up work that doesn't throw exceptions here. 
    virtual ~MemoryLeakDetectorPocTest(); 

    // Code here will be called immediately after the constructor (right 
    // before each test). 
    virtual void SetUp(); 

    // Code here will be called immediately after each test (right 
    // before the destructor). 
    virtual void TearDown(); 
}; 

आशा है कि यह उपयोगी है और मुझे पता है जो कुछ भी स्पष्ट नहीं है वहाँ है अगर बताएं।

चीयर्स,

गाय।

+1

यह स्टैक ओवरफ़्लो पर ठीक है - यहां तक ​​कि प्रोत्साहित किया गया है, एक प्रश्न पूछने के लिए और फिर इसे स्वयं जवाब दें, इस मामले में मैं इसकी अनुशंसा करता हूं क्योंकि इस उत्तर में मूल प्रश्न द्वारा संबोधित किए गए एक अलग परिदृश्य को शामिल किया गया है (और इसके टैग) –

संबंधित मुद्दे