Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
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 memcpy
gö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 memcpy
için belirtilenler:
"
memcpy
src'den dest'e bayt sayısını kopyalar;wmemcpy
kopyalar, geniş karakterleri (iki bayt) sayar. Kaynak ve hedef çakışıyorsa,memcpy
öğesinin davranışı tanımsızdır. Çakışan bölgeleri işlemek için kullanınmemmove
.
Ö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
, baytlarıncount
kaynak arabellekten hedef arabelleğe kopyalanmasını sağlar.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 count
arası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ı wmemcpy
yazdığı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, yaygın bir off-by-one hatası 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 Açıklaması | Açıklama |
---|---|---|
Çağrılan fonksiyona giriş | _In_ |
Veriler çağrılan işleve geçirilir ve salt okunur olarak kabul edilir. |
Çağrılan işleve giriş ve çağırana çı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ğırıcıya yönelik işaretçinin çıktısı | _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 fonksiyona giriş | _In_ |
_In_opt_ |
Çağrılan işleve giriş ve çağırana çıkış | _Inout_ |
_Inout_opt_ |
Çağıranın çıktısı | _Out_ |
_Out_opt_ |
Çağırıcıya yönelik işaretçinin çıktısı | _Outptr_ |
_Outptr_opt_ |
Bu ek açıklamalar, olası başlatılmamış değerleri ve geçersiz boş 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 kod çözümleme aracını kullanma
Örneklerde Visual Studio kod çözümleme 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
Visual Studio'da, SAL ek açıklamalarını içeren bir C++ projesi açın.
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_ Açıklama
_In_
açıklama aşağıdakileri belirtir:
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" olduğunu belirtir._In_
, yerine_Inout_
ek açıklamasına sahip olması gereken bir parametreye uygulamak 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 kod analizi kullanırsanız, çağıranların pInt
için başlatılmış bir arabelleğe Null olmayan bir işaretçi geçirdiğini doğrular. Bu durumda, pInt
işaretçi NULL olamaz.
Örnek: _In_opt_ Anotasyonu
_In_opt_
ile _In_
aynıdır. Ancak, giriş parametresinin NULL olmasına izin verildiğinden, işlevin bunu kontrol etmesi 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 kod analizi, işlevin arabelleğe erişmeden önce NULL değerini denetlediğini doğrular.
Örnek: _Out_ 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önüşünden önce başlatacağını garanti 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 kod analizi, çağıranın bir arabelleğe NULL olmayan bir işaretçi geçirdiğini ve işlevin, bu işaretçiyi döndürmeden önce arabelleği başlattığı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 kod analizi, bu işlevin pInt
başvurulmadan önce NULL olup olmadığını kontrol ettiğini ve eğer pInt
NULL değilse, arabelleğin işlev tarafından döndürülmeden önce başlatıldığını doğrular.
Örnek: _Inout_ 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üğünde de geçerli bir değere sahip olmalıdır. Açıklama, işlevin tek öğeli arabelleği serbestçe okuyup yazabileceğini belirtir. Çağıranın arabelleği sağlaması ve başlatması gerekir.
Not
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'nun kod analizi, çağıranların pInt
için başlatılmış bir arabelleğe NULL olmayan bir işaretçi geçirdiğini ve pInt
dönüşünden önce hala NULL olmadığı ve arabelleğin başlatıldığı doğrular.
Örnek: _Inout_opt_ Ek Açıklaması
_Inout_opt_
ile _Inout_
aynıdır. Ancak, giriş parametresinin NULL olmasına izin verildiğinden, işlevin bunu kontrol etmesi 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 kod analizi, 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_ 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 kod analizi, çağırıcının *pInt
için NULL olmayan bir işaretçi geçirdiğini ve arabelleğin işlev 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 kod analizi, bu işlevin, başvurunun yapılmadan önce *pInt
'nın NULL olup olmadığını denetlediğini ve arabelleğin işlevin kendisi tarafından, döndürülmeden önce başlatıldığını doğrular.
Örnek: _Out_ ile Birlikte _Başarı_ 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 örnek, doğru şeyi yapmak için _Success_
'in _Out_
ile nasıl etkileşime girdiğini gösterir. 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;
}
}
Söz konusu _Out_
açıklaması, Visual Studio kod analizinin, çağıranın pInt
için bir arabelleğe NULL olmayan bir işaretçi geçirdiğ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 bilgileri zaten açıklanmıştır. 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ığı açıklamalarını 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.