C'de değişken bildirim yerleştirme


129

Uzun zamandır C'de tüm değişkenlerin işlevin başında bildirilmesi gerektiğini düşündüm. C99'da kuralların C ++ ile aynı olduğunu biliyorum, ancak C89 / ANSI C için değişken bildirim yerleştirme kuralları nelerdir?

Aşağıdaki kod başarıyla gcc -std=c89ve ile derlenir gcc -ansi:

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

Bildirgeleri olmamalı cve sC89 / ANSI modunda bir hataya neden?


54
Sadece bir not: ansi C'deki değişkenlerin bir fonksiyonun başlangıcında değil, bir bloğun başlangıcında bildirilmesi gerekir. Öyleyse, for döngünüzün üstündeki char c = ... ansi C'de tamamen yasaldır. Ancak char * s olmaz.
Jason Coco

Yanıtlar:


149

Başarıyla derlenir çünkü GCC s, C89 veya ANSI standardının bir parçası olmasa bile GNU uzantısı olarak bildirimine izin verir . Bu standartlara kesinlikle uymak istiyorsanız, -pedanticbayrağı geçirmelisiniz.

cBir { }bloğun başlangıcındaki beyanı C89 standardının bir parçasıdır; bloğun bir işlev olması gerekmez.


41
Muhtemelen sadece beyanının sbir uzantı olduğunu belirtmekte fayda var (C89 açısından). Beyanı cC89'da tamamen yasaldır, uzatmaya gerek yoktur.
AnT

7
@AndreyT: Evet, C'de, değişken bildirimleri @ bloğun başlangıcı olmalı ve tek başına bir işlev olmamalıdır; ancak insanlar bloğu fonksiyonla karıştırır çünkü bu bir bloğun birincil örneğidir.
legends2k

1
Yorumu +39 oyla cevaba taşıdım.
MarcH

78

C89 için, bir kapsam bloğunun başında tüm değişkenlerinizi bildirmelisiniz .

Dolayısıyla, char cbildiriminiz for döngüsü kapsam bloğunun en üstünde olduğu için geçerlidir. Ancak char *sbeyan bir hata olmalıdır.


2
Oldukça doğru. Değişkenleri herhangi bir {...} başlangıcında tanımlayabilirsiniz.
Artelius

5
@Artelius Pek doğru değil. Yalnızca curlies bir bloğun parçasıysa (bir yapı veya birleşim bildiriminin parçası veya çaprazlanmış bir başlatıcı değilse)
Jens

Sadece bilgiçlikçi olmak için, hatalı beyan en azından C standardına göre bildirilmelidir. Yani bir hata veya bir uyarı olmalı gcc. Yani, bir programın uyumlu olduğu anlamına gelecek şekilde derlenebileceğine güvenmeyin.
jinawee

35

Değişken bildirimlerini bloğun üst kısmında gruplamak, eski, ilkel C derleyicilerinin sınırlamaları nedeniyle muhtemelen bir mirastır. Tüm modern diller, yerel değişkenlerin bildirimini en son noktada tavsiye eder ve hatta bazen zorunlu kılar: ilk başladıkları yer. Çünkü bu, yanlışlıkla rastgele bir değer kullanma riskinden kurtulur. Bildirimi ve başlatmayı ayırmak, mümkün olduğunda "const" (veya "final") kullanmanızı da engeller.

C ++ ne yazık ki, C ile geriye dönük uyumluluk için eski, en iyi bildirim yöntemini kabul etmeye devam ediyor (diğerlerinden bir C uyumluluğu sürükleniyor ...) Ancak C ++ ondan uzaklaşmaya çalışıyor:

  • C ++ referanslarının tasarımı, bu türden blok gruplamasına bile izin vermez.
  • Bir C ++ yerel nesnesinin bildirimini ve başlatılmasını ayırırsanız, fazladan bir kurucunun maliyetini hiçbir şey için ödersiniz. No-arg kurucusu yoksa, ikisini birden ayırmanıza bile izin verilmez!

C99, C'yi bu yönde hareket ettirmeye başlar.

Yerel değişkenlerin nerede bildirildiğini bulamamaktan endişeleniyorsanız, bu çok daha büyük bir sorununuz olduğu anlamına gelir: çevreleyen blok çok uzun ve bölünmelidir.

https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions



Ayrıca, bloğun tepesindeki değişken bildirimlerini zorlamanın güvenlik açıkları yaratabileceğini görün: lwn.net/Articles/443037
MarcH

"C ++ maalesef C ile geriye dönük uyumluluk için eski, en iyi bildirim yöntemini kabul etmeye devam ediyor": IMHO, bunu yapmanın sadece temiz yolu. Diğer diller bu sorunu her zaman 0 ile başlatarak "çözer". Bzzt, bu sadece bana sorarsanız mantık hatalarını maskeler. Ve başlatma olmadan bildirime İHTİYACINIZ OLAN pek çok durum vardır çünkü başlatma için birden fazla olası konum vardır. İşte bu yüzden C ++ 'ın RAII'si gerçekten çok büyük bir sıkıntıdır - Şimdi bu durumlara izin vermek için her nesneye "geçerli" başlatılmamış bir durum eklemeniz gerekiyor.
Jo So

1
@JoSo: Neden başlatılmamış değişkenleri okumanın keyfi etkiler yarattığını, programlama hatalarını tespit etmeyi tutarlı bir değer veya deterministik bir hata vermesinden daha kolay hale getireceğini düşündüğünüzde kafam karıştı? Bilgisayarsız depolamanın, değişkenin sahip olabileceği herhangi bir bit modeliyle tutarlı bir şekilde davranacağının ve hatta böyle bir programın olağan zaman ve nedensellik yasalarıyla tutarlı bir şekilde davranacağının garantisi olmadığını unutmayın. Gibi bir şey verildiğinde int y; ... if (x) { printf("X was true"); y=23;} return y;...
supercat

1
@JoSo: İşaretçiler için, özellikle de işlemleri tuzağa düşüren uygulamalarda null, all-bit-zero genellikle yararlı bir tuzak değeridir. Ayrıca, değişkenlerin tüm bit sıfıra varsayılan olduğunu açıkça belirten dillerde, bu değere güvenmek bir hata değildir . Derleyiciler henüz "optimizasyonları" konusunda aşırı derecede tuhaf olma eğiliminde değiller , ancak derleyici yazarları gittikçe daha fazla akıllı olmaya çalışıyorlar. Değişkenleri kasıtlı sözde rasgele değişkenlerle başlatmak için bir derleyici seçeneği, hataları tanımlamak için yararlı olabilir, ancak yalnızca son değerini tutan depolamayı bırakmak bazen hataları maskeleyebilir.
supercat

22

Sözdizimsel değil, sürdürülebilirlik açısından bakıldığında, en az üç düşünce dizisi vardır:

  1. Tüm değişkenleri işlevin başında belirtin, böylece tek bir yerde olurlar ve kapsamlı listeyi bir bakışta görebilirsiniz.

  2. Tüm değişkenleri ilk kullanıldıkları yere olabildiğince yakın olarak beyan edin, böylece her birinin neden gerekli olduğunu bilirsiniz .

  3. En içteki kapsam bloğunun başlangıcında tüm değişkenleri bildirin, böylece mümkün olan en kısa sürede kapsam dışına çıkacaklar ve derleyicinin belleği optimize etmesine ve istemediğiniz yerde yanlışlıkla kullanıp kullanmadığınızı size söylemesine izin vereceklerdir.

Genelde ilk seçeneği tercih ederim, çünkü diğerleri beni beyanlar için kod araştırmaya zorluyor. Tüm değişkenlerin önceden tanımlanması, onları bir hata ayıklayıcıdan başlatmayı ve izlemeyi de kolaylaştırır.

Bazen değişkenleri daha küçük bir kapsam bloğu içinde bildiririm, ancak yalnızca çok azına sahip olduğum İyi Bir Neden için. Bir örnek fork(), yalnızca alt süreç tarafından ihtiyaç duyulan değişkenleri bildirmek için a'dan sonra olabilir . Bana göre bu görsel gösterge, amaçlarının faydalı bir hatırlatıcısıdır.


27
Değişkenleri bulmanın daha kolay olması için seçenek 2 veya 3'ü kullanıyorum - çünkü işlevler değişken bildirimlerini göremeyecek kadar büyük olmamalı.
Jonathan Leffler

8
70'lerden bir derleyici kullanmadığınız sürece Seçenek 3 sorun teşkil etmez.
edgar.holleis

15
İyi bir IDE kullandıysanız, kod aramaya gitmenize gerek kalmaz çünkü beyanı sizin için bulmak için bir IDE komutu olmalıdır. (Eclipse'de F3)
edgar.holleis

4
Seçenek 1'de başlatmayı nasıl sağlayabileceğinizi anlamıyorum, bazen başlangıç ​​değerini blokta daha sonra başka bir işlevi çağırarak veya bir katulasyon gerçekleştirerek alabilirsiniz.
Plumenator

4
@Plumenator: 1. seçenek başlatmayı garanti etmez; Onları bildirim üzerine, ya "doğru" değerlerine ya da uygun şekilde ayarlanmadıkları takdirde sonraki kodun kırılacağını garanti edecek bir şeye başlatmayı seçtim. "Seçti" diyorum çünkü bunu yazdığımdan beri tercihim # 2 olarak değişti, belki şu anda Java'yı C'den daha fazla kullandığım için ve daha iyi geliştirme araçlarına sahip olduğum için.
Adam Liss

6

Başkalarının da belirttiği gibi, "bilgiçlik" kontrolü kullanmadığınız sürece, "C89" modunda bile GCC bu konuda (ve çağrıldıkları argümanlara bağlı olarak muhtemelen diğer derleyiciler) müsaade edicidir. Dürüst olmak gerekirse, bilgiçlik yapmamak için pek çok iyi neden yok; Kaliteli modern kod her zaman uyarı olmadan derlenmelidir (veya çok azı, derleyiciye olası bir hata olarak şüpheli bir şey yaptığınızı bildiğiniz yerlerde), bu nedenle kodunuzu bilgiçlik taslayan bir kurulumla derleyemezseniz, muhtemelen biraz dikkat edilmesi gerekir.

C89, değişkenlerin her kapsamdaki diğer ifadelerden önce bildirilmesini gerektirir, daha sonraki standartlar kullanıma daha yakın bildirime izin verir (bu hem daha sezgisel hem de daha verimli olabilir), özellikle 'for' döngülerinde bir döngü kontrol değişkeninin eşzamanlı bildirimi ve başlatılması.


0

Belirtildiği gibi, bu konuda iki düşünce ekolü vardır.

1) Yıl 1987 olduğu için her şeyi işlevlerin başında ilan edin.

2) İlk kullanıma en yakın şekilde ve mümkün olan en küçük kapsamda beyan edin.

Buna cevabım İKİSİNİ YAPIN! Açıklamama izin ver:

Uzun işlevler için, 1) yeniden düzenlemeyi çok zorlaştırır. Geliştiricilerin alt yordamlar fikrine karşı olduğu bir kod tabanında çalışıyorsanız, işlevin başlangıcında 50 değişken bildiriminiz olur ve bunlardan bazıları, bir for-döngü için yalnızca bir "i" olabilir. işlevin altında.

Bu yüzden bundan en üstte TSSB beyanı geliştirdim ve 2. seçeneği dini olarak yapmaya çalıştım.

Bir şey yüzünden birinci seçeneğe geri döndüm: kısa işlevler. İşlevleriniz yeterince kısaysa, o zaman birkaç yerel değişkene sahip olursunuz ve işlev kısa olduğu için, onları işlevin en üstüne koyarsanız, yine de ilk kullanıma yakın olurlar.

Ayrıca, en üstte bildirmek istediğinizde ancak başlatma için gerekli bazı hesaplamaları yapmadığınızda "bildirmek ve NULL olarak ayarlamak" karşıt kalıbı çözülür çünkü başlatmanız gereken şeyler büyük olasılıkla argüman olarak alınacaktır.

Yani şimdi benim düşüncem, işlevlerin en üstünde ve ilk kullanıma mümkün olduğunca yakın beyan etmeniz gerektiğidir. Yani HER İKİSİ de! Ve bunu yapmanın yolu iyi bölünmüş alt yordamlardır.

Ancak uzun bir işlev üzerinde çalışıyorsanız, o zaman ilk kullanıma en yakın şeyleri koyun, çünkü bu şekilde yöntemleri çıkarmak daha kolay olacaktır.

Tarifim bu. Tüm yerel değişkenler için, değişkeni alın ve bildirimini en alta taşıyın, derleyin, ardından bildirimi derleme hatasından hemen öncesine taşıyın. Bu ilk kullanım. Bunu tüm yerel değişkenler için yapın.

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Şimdi, bildirimden önce başlayan ve program derlenene kadar sonu hareket ettiren bir kapsam bloğu tanımlayın

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

Bu derlenmez çünkü foo kullanan daha fazla kod vardır. Derleyicinin foo kullanmadığı için bar kullanan kodun içinden geçebildiğini fark edebiliriz. Bu noktada iki seçenek var. Mekanik olan, derlenene kadar "}" karakterini aşağı doğru hareket ettirmek, diğer seçenek ise kodu incelemek ve sıranın şu şekilde değiştirilip değiştirilemeyeceğini belirlemektir:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

Sıra değiştirilebilirse, muhtemelen istediğiniz şey budur çünkü geçici değerlerin ömrünü kısaltır.

Unutulmaması gereken bir diğer husus, foo'nun değerinin onu kullanan kod blokları arasında korunması mı gerekir, yoksa her ikisinde de farklı bir foo olabilir mi? Örneğin

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

Bu durumların benim prosedürümden daha fazlasına ihtiyacı var. Geliştiricinin ne yapacağını belirlemek için kodu analiz etmesi gerekecektir.

Ancak ilk adım, ilk kullanımı bulmaktır. Bunu görsel olarak yapabilirsiniz, ancak bazen bildirimi silmek, derlemeye çalışmak ve ilk kullanımın üzerine geri koymak daha kolaydır. Bu ilk kullanım bir if ifadesinin içindeyse, oraya koyun ve derlenip derlenmediğini kontrol edin. Derleyici daha sonra diğer kullanımları belirleyecektir. Her iki kullanımı da kapsayan bir kapsam bloğu oluşturmaya çalışın.

Bu mekanik kısım yapıldıktan sonra verilerin nerede olduğunu analiz etmek kolaylaşır. Bir değişken büyük bir kapsam bloğunda kullanılıyorsa, durumu analiz edin ve aynı değişkeni iki farklı şey için kullanıp kullanmadığınızı görün (döngüler için iki kişi için kullanılan bir "i" gibi). Kullanımlar ilgisiz ise, bu ilgisiz kullanımların her biri için yeni değişkenler oluşturun.


0

Tüm değişkeni üstte veya işlevde "yerel olarak" tanımlamalısınız. Cevap:

Duruma göre kullandığınız tür neyi size sistem üzerinde:

1 / Gömülü Sistem (özellikle Uçak veya Araba gibi yaşamlarla ilgili): Dinamik bellek kullanmanıza izin verir (örneğin: calloc, malloc, yeni ...). 1000 mühendisle çok büyük bir projede çalıştığınızı hayal edin. Ya yeni dinamik bellek ayırırlarsa ve onu kaldırmayı unuturlarsa (artık kullanılmadığında)? Gömülü sistem uzun süre çalışırsa yığın taşmasına neden olur ve yazılım bozulur. Kaliteden emin olmak kolay değil (en iyi yol dinamik belleği yasaklamaktır).

Bir Uçak 30 gün içinde çalışır ve dönmezse, yazılım bozulursa (uçak hala havadayken) ne olur?

2 / Web, PC gibi diğer sistemler (büyük hafıza alanına sahip):

Kullanarak belleği optimize etmek için değişkeni "yerel olarak" bildirmelisiniz. Bu sistem uzun süre çalışırsa ve yığın taşması meydana gelirse (çünkü birisi dinamik belleği kaldırmayı unuttu). Bilgisayarı sıfırlamak için basit olanı yapın: P Hayatları etkilemez


Bunun doğru olduğundan emin değilim. Sanırım tüm yerel değişkenlerinizi tek bir yerde bildirirseniz bellek sızıntılarını denetlemenin daha kolay olduğunu söylüyorsunuz? Bu doğru olabilir ama satın aldığımdan pek emin değilim. Madde (2) 'ye gelince, değişkeni yerel olarak bildirmenin "bellek kullanımını optimize edeceğini" mi söylüyorsunuz? Bu teorik olarak mümkündür. Bir derleyici, bellek kullanımını en aza indirmek için bir işlev boyunca yığın çerçevesini yeniden boyutlandırmayı seçebilir, ancak bunu yapanların farkında değilim. Gerçekte, derleyici tüm "yerel" bildirimleri "perde arkasında işlev başlangıcı" na dönüştürür.
QuinnFreedman

1 / Gömülü sistem bazen dinamik belleğe izin vermez, bu nedenle tüm değişkeni işlevin tepesinde bildirirseniz. Kaynak kodu oluşturulduğunda, programı çalıştırmak için yığında ihtiyaç duydukları bayt sayısını hesaplayabilir. Ancak dinamik bellek ile derleyici aynı şeyi yapamaz.
Dang_Ho

2 / Bir değişkeni yerel olarak bildirirseniz, bu değişken yalnızca "{}" açma / kapama köşeli ayraç içinde bulunur. Böylece derleyici, eğer bu değişken "kapsam dışı" ise, değişken alanını serbest bırakabilir. Bu, her şeyi işlevin tepesinde bildirmekten daha iyi olabilir.
Dang_Ho

Statik ve dinamik bellek konusunda kafanızın karıştığını düşünüyorum. Statik bellek yığın üzerinde ayrılmıştır. Bir işlevde bildirilen tüm değişkenler, nerede bildirildiklerine bakılmaksızın statik olarak tahsis edilir. Dinamik bellek, benzeri bir şeyle yığın üzerinde tahsis edilir malloc(). Daha önce bunu yapamayan bir cihaz görmemiş olsam da, en iyi uygulama gömülü sistemlerde dinamik ayırmadan kaçınmaktır ( buraya bakın ). Ancak bunun değişkenlerinizi bir işlevde açıkladığınız yerle ilgisi yoktur.
QuinnFreedman

1
Bunun makul bir çalışma şekli olacağını kabul etsem de, pratikte olan bu değil. İşte sizin örneğinize çok benzeyen bir şey için gerçek montaj: godbolt.org/z/mLhE9a . Gördüğünüz gibi, 11. satırda if ifadesinin dışındakisub rsp, 1008 tüm dizi için yer ayırıyor. Bu için de geçerlidir ve denedim her sürümü ve optimizasyon düzeyinde. clanggcc
QuinnFreedman

-1

Açık bir açıklama için gcc sürüm 4.7.0 kılavuzundan bazı ifadeleri alıntılayacağım.

"Derleyici, 'c90' veya 'c ++ 98' gibi birkaç temel standardı ve 'gnu90' veya 'gnu ++ 98' gibi bu standartların GNU lehçelerini kabul edebilir. Temel bir standart belirleyerek derleyici bu standardı takip eden ve onunla çelişmeyen GNU uzantılarını kullanan tüm programları kabul edecektir.Örneğin, '-std = c90' GCC'nin asm ve typeof anahtar sözcükleri gibi ISO C90 ile uyumlu olmayan bazı özelliklerini kapatır, ancak Bir?: ifadesinin orta terimini çıkarmak gibi ISO C90'da bir anlamı olmayan diğer GNU uzantıları. "

Sanırım sorunuzun kilit noktası, "-std = c89" seçeneği kullanılsa bile gcc'nin neden C89'a uymadığıdır. Gcc'nizin sürümünü bilmiyorum, ancak büyük bir fark olmayacağını düşünüyorum. Gcc'nin geliştiricisi bize "-std = c89" seçeneğinin sadece C89 ile çelişen uzantıların kapalı olduğu anlamına geldiğini söyledi. Yani C89'da bir anlamı olmayan bazı eklentilerle ilgisi yok. Ve değişken bildiriminin yerleşimini kısıtlamayan uzantı, C89 ile çelişmeyen uzantılara aittir.

Dürüst olmak gerekirse, "-std = c89" seçeneğinin ilk bakışta herkes C89'a tamamen uyması gerektiğini düşünecektir. Ama öyle değil. Başlangıçta tüm değişkenlerin daha iyi veya daha kötü olduğunu bildiren soruna gelince, sadece bir alışkanlık meselesidir.


uyumlu olmak uzantıları kabul etmemek anlamına gelmez: derleyici geçerli programları derlediği ve diğerleri için gerekli tanılamayı ürettiği sürece uyumludur.
Monica'yı hatırla

@Marc Lehmann, evet, derleyicileri ayırt etmek için "uygunluk" kelimesi kullanıldığında haklısınız. Ancak bazı kullanımları tanımlamak için "uygunluk" kelimesi kullanıldığında, "Bir kullanım standarda uymuyor" diyebilirsiniz. Ve tüm yeni başlayanlar, standarda uymayan kullanımların bir hataya neden olması gerektiği görüşüne sahiptir.
junwanghe

@Marc Lehmann, bu arada, gcc'nin C89 standardına uymayan bir kullanım gördüğünde herhangi bir teşhis yoktur.
junwanghe

Cevabınız hala yanlış, çünkü "gcc uymuyor" iddiasıyla "bazı kullanıcı programları uymuyor" aynı şey değil. Uygunluk kullanımınız tamamen yanlıştır. Ayrıca, ben başlangıç ​​seviyesindeyken belirttiğiniz görüşte değildim, bu da yanlış. Son olarak, uyumlu bir derleyicinin uygun olmayan kodu teşhis etmesi için herhangi bir gereksinim yoktur ve aslında bunun uygulanması imkansızdır.
Monica'yı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.