Aracılığıyla paylaş


SAL'ı Anlama

Microsoft kaynak kodu ek açıklama dili (SAL), bir işlevin parametrelerini nasıl kullandığını, bunlar hakkında yaptığı varsayımları ve tamamlandığında yaptığı garantileri açıklamak için kullanabileceğiniz bir dizi ek açıklama sağlar. Ek açıklamalar üst bilgi dosyasında <sal.h>tanımlanır. C++ için Visual Studio kod analizi, işlevlerin analizini değiştirmek için SAL ek açıklamalarını kullanır. Windows sürücü geliştirmesi için SAL 2.0 hakkında daha fazla bilgi için bkz . Windows Sürücüleri için SAL 2.0 Ek Açıklamaları.

Yerel olarak, C ve C++ geliştiricilerin amaç ve sabitliği tutarlı bir şekilde ifade etmek için yalnızca sınırlı yollar sağlar. SAL ek açıklamalarını kullanarak işlevlerinizi daha ayrıntılı bir şekilde açıklayabilirsiniz, böylece bunları kullanan geliştiriciler bunları nasıl kullanacaklarını daha iyi anlayabilir.

SAL Nedir ve Neden Kullanmalısınız?

Basitçe belirtmek gerekirse, SAL derleyicinin kodunuzu sizin yerinize denetlemesine izin vermenin ucuz bir yoludur.

SAL Kodu Daha Değerli Hale Getirir

SAL, hem insanlar hem de kod analizi araçları için kod tasarımınızı daha anlaşılır hale getirmenize yardımcı olabilir. C çalışma zamanı işlevini memcpygösteren bu örneği göz önünde bulundurun:

void * memcpy(
   void *dest,
   const void *src,
   size_t count
);

Bu işlevin ne yaptığını söyleyebilir misiniz? Bir işlev uygulandığında veya çağrıldığında, program doğruluğunu sağlamak için belirli özelliklerin korunması gerekir. Örnekteki gibi bir bildirime bakarak bunların ne olduğunu bilmezsiniz. SAL ek açıklamaları olmadan belgelere veya kod açıklamalarına güvenmeniz gerekir. Belgelerde şu ifadeler yer aldı memcpy :

"memcpy src'den dest'e bayt sayısını kopyalar; wmemcpykopyalar, geniş karakterleri (iki bayt) sayar. Kaynak ve hedef çakışıyorsa, öğesinin memcpy davranışı tanımsız olur. Çakışan bölgeleri işlemek için kullanın memmove .
Önemli: Hedef arabelleğin kaynak arabellekten aynı boyutta veya daha büyük olduğundan emin olun. Daha fazla bilgi için bkz. Arabellek Taşmalarından Kaçınma."

Belgelerde, program doğruluğundan emin olmak için kodunuzun belirli özellikleri tutması gereken birkaç bilgi bulunur:

  • memcpy bayt sayısını count kaynak arabellekten hedef arabelleğe kopyalar.

  • Hedef arabellek en az kaynak arabellek kadar büyük olmalıdır.

Ancak, derleyici belgeleri veya resmi olmayan açıklamaları okuyamaz. İki arabellek ile countarasında bir ilişki olduğunu bilmez ve bir ilişkiyi etkili bir şekilde tahmin etmez. SAL, burada gösterildiği gibi işlevin özellikleri ve uygulaması hakkında daha fazla netlik sağlayabilir:

void * memcpy(
   _Out_writes_bytes_all_(count) void *dest,
   _In_reads_bytes_(count) const void *src,
   size_t count
);

Bu ek açıklamaların belgelerdeki bilgilere benzediklerine dikkat edin, ancak daha kısadır ve anlamsal bir desen izlerler. Bu kodu okurken, bu işlevin özelliklerini ve arabellek taşması güvenlik sorunlarını nasıl önleyebileceğinizi hızlı bir şekilde anlayabilirsiniz. Daha da iyisi, SAL'nin sağladığı anlamsal desenler, olası hataların erken keşfedilmesinde otomatik kod analizi araçlarının verimliliğini ve verimliliğini artırabilir. Birinin bu buggy uygulamasını wmemcpyyazdığını düşünün:

wchar_t * wmemcpy(
   _Out_writes_all_(count) wchar_t *dest,
   _In_reads_(count) const wchar_t *src,
   size_t count)
{
   size_t i;
   for (i = 0; i <= count; i++) { // BUG: off-by-one error
      dest[i] = src[i];
   }
   return dest;
}

Bu uygulama ortak bir hata içeriyor. Neyse ki, kod yazarı SAL arabellek boyutu ek açıklamasını ekledi; bir kod çözümleme aracı bu işlevi tek başına analiz ederek hatayı yakalayabilir.

SAL Temel Bilgileri

SAL, kullanım düzenine göre kategorilere ayrılmış dört temel parametre türünü tanımlar.

Kategori Parametre Ek Açıklaması Tanım
Çağrılan işleve giriş _In_ Veriler çağrılan işleve geçirilir ve salt okunur olarak kabul edilir.
Çağrılan işleve giriş ve çağıranın çıkışı _Inout_ Kullanılabilir veriler işleve geçirilir ve potansiyel olarak değiştirilir.
Çağıranın çıktısı _Out_ Çağıran yalnızca çağrılan işlevin yazılabilmesi için alan sağlar. Çağrılan işlev bu alana veri yazar.
Çağıranın işaretçisinin çıkışı _Outptr_ Çağıranın çıktısı gibi. Çağrılan işlev tarafından döndürülen değer bir işaretçidir.

Bu dört temel ek açıklama çeşitli yollarla daha açık hale getirilebilir. Varsayılan olarak, açıklamalı işaretçi parametrelerinin gerekli olduğu varsayılır; işlevin başarılı olması için NULL olmayan olması gerekir. Temel ek açıklamaların en yaygın olarak kullanılan varyasyonu, işaretçi parametresinin isteğe bağlı olduğunu gösterir; NULL ise, işlev çalışmasını yaparken yine de başarılı olabilir.

Bu tabloda, gerekli ve isteğe bağlı parametreler arasında nasıl ayrım yapılır gösterilmektedir:

Parametreler gereklidir Parametreler isteğe bağlıdır
Çağrılan işleve giriş _In_ _In_opt_
Çağrılan işleve giriş ve çağıranın çıkışı _Inout_ _Inout_opt_
Çağıranın çıktısı _Out_ _Out_opt_
Çağıranın işaretçisinin çıkışı _Outptr_ _Outptr_opt_

Bu ek açıklamalar, olası başlatılmamış değerleri ve geçersiz null işaretçi kullanımlarını resmi ve doğru bir şekilde tanımlamaya yardımcı olur. Null değerinin gerekli bir parametreye geçirilmesi kilitlenmeye veya "başarısız" hata kodunun döndürülmesine neden olabilir. Her iki durumda da işlev işini yaparken başarılı olamaz.

SAL Örnekleri

Bu bölümde, temel SAL ek açıklamaları için kod örnekleri gösterilmektedir.

Hataları Bulmak için Visual Studio Code Çözümleme Aracı'nı kullanma

Örneklerde Visual Studio Code Analysis aracı, kod hatalarını bulmak için SAL ek açıklamaları ile birlikte kullanılır. Bunu şu şekilde yapabilirsiniz.

Visual Studio kod çözümleme araçlarını ve SAL'yi kullanmak için

  1. Visual Studio'da, SAL ek açıklamalarını içeren bir C++ projesi açın.

  2. Menü çubuğunda Derle, Çözümde Kod Analizini Çalıştır'ı seçin.

    Bu bölümdeki _In_ örneğini göz önünde bulundurun. Üzerinde kod analizi çalıştırırsanız, bu uyarı görüntülenir:

    C6387 Geçersiz Parametre Değeri 'pInt' '0' olabilir: Bu, 'InCallee' işlevinin belirtimine uymaz.

Örnek: _In_ Ek Açıklaması

Ek _In_ açıklama aşağıdakileri gösterir:

  • Parametre geçerli olmalıdır ve değiştirilmez.

  • İşlev yalnızca tek öğeli arabellekten okur.

  • Çağıranın arabelleği sağlaması ve başlatması gerekir.

  • _In_ "salt okunur" ifadesini belirtir. Bunun yerine ek açıklamaya sahip olması gereken bir parametreye _Inout_ uygulamak _In_ yaygın bir hatadır.

  • _In_ izin verilir ancak işaretçi olmayan skalerlerde çözümleyici tarafından yoksayılır.

void InCallee(_In_ int *pInt)
{
   int i = *pInt;
}

void GoodInCaller()
{
   int *pInt = new int;
   *pInt = 5;

   InCallee(pInt);
   delete pInt;
}

void BadInCaller()
{
   int *pInt = NULL;
   InCallee(pInt); // pInt should not be NULL
}

Bu örnekte Visual Studio Code Analysis kullanıyorsanız, çağıranların için pIntbaşlatılan arabelleğe Null olmayan bir işaretçi geçirdiğini doğrular. Bu durumda, pInt işaretçi NULL olamaz.

Örnek: _In_opt_ Ek Açıklaması

_In_opt_ ile aynıdır _In_, ancak giriş parametresinin NULL olması ve bu nedenle işlevin bunu denetlemesi gerekir.

void GoodInOptCallee(_In_opt_ int *pInt)
{
   if(pInt != NULL) {
      int i = *pInt;
   }
}

void BadInOptCallee(_In_opt_ int *pInt)
{
   int i = *pInt; // Dereferencing NULL pointer 'pInt'
}

void InOptCaller()
{
   int *pInt = NULL;
   GoodInOptCallee(pInt);
   BadInOptCallee(pInt);
}

Visual Studio Code Analizi, işlevin arabelleğe erişmeden önce NULL değerini denetlediğini doğrular.

Örnek: _Out_ Ek Açıklaması

_Out_ , bir öğe arabelleğine işaret eden NULL olmayan bir işaretçinin geçirildiği ve işlevin öğeyi başlatdığı yaygın bir senaryoyu destekler. Çağıranın çağrıdan önce arabelleği başlatması gerekmez; çağrılan işlev, döndürmeden önce başlatmayı vaat eder.

void GoodOutCallee(_Out_ int *pInt)
{
   *pInt = 5;
}

void BadOutCallee(_Out_ int *pInt)
{
   // Did not initialize pInt buffer before returning!
}

void OutCaller()
{
   int *pInt = new int;
   GoodOutCallee(pInt);
   BadOutCallee(pInt);
   delete pInt;
}

Visual Studio Code Çözümleme Aracı, çağıranın bir arabelleğe pInt NULL olmayan bir işaretçi geçirip döndürmeden önce arabelleğin işlev tarafından başlatıldığını doğrular.

Örnek: _Out_opt_ Ek Açıklaması

_Out_opt_ , parametresinin NULL olması için izin verilmesi dışında ile aynıdır _Out_ve bu nedenle işlevin bunu denetlemesi gerekir.

void GoodOutOptCallee(_Out_opt_ int *pInt)
{
   if (pInt != NULL) {
      *pInt = 5;
   }
}

void BadOutOptCallee(_Out_opt_ int *pInt)
{
   *pInt = 5; // Dereferencing NULL pointer 'pInt'
}

void OutOptCaller()
{
   int *pInt = NULL;
   GoodOutOptCallee(pInt);
   BadOutOptCallee(pInt);
}

Visual Studio Code Çözümlemesi, bu işlevin başvurulmadan önce pInt NULL olup olmadığını denetlediğini ve NULL değilse pInt , arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrular.

Örnek: _Inout_ Ek Açıklaması

_Inout_ işlevi tarafından değiştirilebilen bir işaretçi parametresine açıklama eklemek için kullanılır. İşaretçi, çağrıdan önce geçerli başlatılan verilere işaret etmelidir ve değişse bile, döndürülen geçerli bir değere sahip olmalıdır. Ek açıklama işlevin tek öğeli arabelleğe serbestçe okunabileceğini ve yazabileceğini belirtir. Çağıranın arabelleği sağlaması ve başlatması gerekir.

Dekont

gibi _Out_, _Inout_ değiştirilebilir bir değere uygulanmalıdır.

void InOutCallee(_Inout_ int *pInt)
{
   int i = *pInt;
   *pInt = 6;
}

void InOutCaller()
{
   int *pInt = new int;
   *pInt = 5;
   InOutCallee(pInt);
   delete pInt;
}

void BadInOutCaller()
{
   int *pInt = NULL;
   InOutCallee(pInt); // 'pInt' should not be NULL
}

Visual Studio Code Çözümlemesi, çağıranların için pIntbaşlatılan bir arabelleğe NULL olmayan bir işaretçi geçirdiğini ve dönüşten pInt önce hala NULL olmayan bir işaretçi olduğunu doğrular ve arabellek başlatılır.

Örnek: _Inout_opt_ Ek Açıklaması

_Inout_opt_ ile aynıdır _Inout_, ancak giriş parametresinin NULL olması ve bu nedenle işlevin bunu denetlemesi gerekir.

void GoodInOutOptCallee(_Inout_opt_ int *pInt)
{
   if(pInt != NULL) {
      int i = *pInt;
      *pInt = 6;
   }
}

void BadInOutOptCallee(_Inout_opt_ int *pInt)
{
   int i = *pInt; // Dereferencing NULL pointer 'pInt'
   *pInt = 6;
}

void InOutOptCaller()
{
   int *pInt = NULL;
   GoodInOutOptCallee(pInt);
   BadInOutOptCallee(pInt);
}

Visual Studio Code Analysis, bu işlevin arabelleğe erişmeden önce NULL olup olmadığını denetlediğini ve NULL değilse pInt , arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrular.

Örnek: _Outptr_ Ek Açıklaması

_Outptr_ , işaretçi döndürmek için amaçlanan bir parametreye açıklama eklemek için kullanılır. Parametrenin kendisi NULL olmamalıdır ve çağrılan işlev içinde NULL olmayan bir işaretçi döndürür ve bu işaretçi başlatılan verileri gösterir.

void GoodOutPtrCallee(_Outptr_ int **pInt)
{
   int *pInt2 = new int;
   *pInt2 = 5;

   *pInt = pInt2;
}

void BadOutPtrCallee(_Outptr_ int **pInt)
{
   int *pInt2 = new int;
   // Did not initialize pInt buffer before returning!
   *pInt = pInt2;
}

void OutPtrCaller()
{
   int *pInt = NULL;
   GoodOutPtrCallee(&pInt);
   BadOutPtrCallee(&pInt);
}

Visual Studio Code Çözümlemesi, çağıranın için *pIntNULL olmayan bir işaretçi geçtiğini ve arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrular.

Örnek: _Outptr_opt_ Ek Açıklaması

_Outptr_opt_ , parametresinin isteğe bağlı olması dışında ile aynıdır _Outptr_; çağıran parametre için NULL işaretçisi geçirebilir.

void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
   int *pInt2 = new int;
   *pInt2 = 6;

   if(pInt != NULL) {
      *pInt = pInt2;
   }
}

void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
   int *pInt2 = new int;
   *pInt2 = 6;
   *pInt = pInt2; // Dereferencing NULL pointer 'pInt'
}

void OutPtrOptCaller()
{
   int **ppInt = NULL;
   GoodOutPtrOptCallee(ppInt);
   BadOutPtrOptCallee(ppInt);
}

Visual Studio Code Analysis, bu işlevin başvurulmadan önce *pInt NULL olup olmadığını denetlediğini ve arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrular.

Örnek: _Out_ ile Birlikte _Success_ Ek Açıklaması

Ek açıklamalar çoğu nesneye uygulanabilir. Özellikle bir işlevin tamamına açıklama ekleyebilirsiniz. Bir işlevin en belirgin özelliklerinden biri, başarılı veya başarısız olmasıdır. Ancak bir arabellek ile boyutu arasındaki ilişki gibi, C/C++ işlevin başarısını veya başarısızlığını ifade edemez. Ek açıklamayı _Success_ kullanarak, bir işlev için başarının nasıl göründüğünü söyleyebilirsiniz. Ek açıklama parametresi _Success_ , true olduğunda işlevin başarılı olduğunu gösteren bir ifadedir. İfade, ek açıklama ayrıştırıcısının işleyebileceği herhangi bir şey olabilir. ek açıklamaların işlev döndürdüğünden sonraki etkileri yalnızca işlev başarılı olduğunda geçerlidir. Bu örnekte, doğru şeyi yapmak için nasıl etkileşime _Out_ geçtiğini gösterir_Success_. Dönüş değerini temsil etmek için anahtar sözcüğünü return kullanabilirsiniz.

_Success_(return != false) // Can also be stated as _Success_(return)
bool GetValue(_Out_ int *pInt, bool flag)
{
   if(flag) {
      *pInt = 5;
      return true;
   } else {
      return false;
   }
}

Ek _Out_ açıklama, Visual Studio Code Analysis'in çağıranın için pIntbir arabelleğe NULL olmayan bir işaretçi geçirip geçirmediğini ve arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrulamasına neden olur.

SAL En İyi Uygulaması

Mevcut Koda Ek Açıklamalar Ekleme

SAL, kodunuzun güvenliğini ve güvenilirliğini artırmanıza yardımcı olabilecek güçlü bir teknolojidir. SAL'yi öğrendikte yeni beceriyi günlük çalışmanıza uygulayabilirsiniz. Yeni kodda, tasarım gereği SAL tabanlı belirtimleri kullanabilirsiniz; eski kodda artımlı olarak ek açıklamalar ekleyebilir ve böylece her güncelleştirdiğinizde avantajları artırabilirsiniz.

Microsoft genel üst bilgilerine zaten açıklama ekleniyor. Bu nedenle, projelerinizde en iyi şekilde yararlanmak için önce Win32 API'lerini çağıran yaprak düğüm işlevlerine ve işlevlerine açıklama eklemenizi öneririz.

Ne Zaman Açıklama Ekleyebilirim?

Bazı yönergeler şunlardır:

  • Tüm işaretçi parametrelerine açıklama ekleme.

  • Kod Analizi'nin arabellek ve işaretçi güvenliğini sağlayabilmesi için değer aralığı ek açıklamalarına açıklama ekleyin.

  • Kilitleme kurallarına ve kilitleme yan efektlerine açıklama ekleme. Daha fazla bilgi için bkz . Kilitleme Davranışına Açıklama Ekleme.

  • Sürücü özelliklerine ve etki alanına özgü diğer özelliklere açıklama ekleyin.

İsterseniz amacınızı net bir şekilde ifade etmek ve ek açıklamaların yapıldığını denetlemeyi kolaylaştırmak için tüm parametrelere açıklama ekleyebilirsiniz.

Ayrıca bkz.