Arduino'da küresel değişkenler kötü mü?


24

Programlamada nispeten yeniyim ve okuduğum en iyi kodlama uygulamalarının birçoğu, global bir değişken kullanmak için çok az iyi neden olduğunu (ya da en iyi kodun hiçbir şekilde küresel olmadığını) belirtiyor.

Bunu akılda tutmak için elimden gelenin en iyisini yaptım, bir SD kart ile bir Arduino arayüzü oluşturmak için yazılım yazarken, bir bilgisayarla konuşun ve bir motor kontrolörü çalıştırın.

Şu anda yaklaşık 1100 satırlık “başlangıç ​​seviyesi” kodu için 46 küreye sahibim (satırda birden fazla işlem yapılmadı). Bu iyi bir oran mı yoksa daha fazla azaltmaya mı bakmalıyım? Ayrıca, küre sayısını daha da azaltmak için hangi uygulamaları kullanabilirim?

Bunu burada soruyorum çünkü genel olarak bilgisayar programlaması yerine Arduino ürünlerine kodlama için en iyi uygulamalarla özellikle ilgileniyorum.


2
Arduino'da global değişkenlerden kaçınamazsınız. Bir işlev / yöntem kapsamı dışındaki her değişken bildirimi geneldir. Dolayısıyla, fonksiyonlar arasında değerleri paylaşmanız gerekiyorsa, her bir değeri argüman olarak iletmek istemediğiniz sürece, küresel olmaları gerekir.

16
@LookAlterno Err, tuhaf makroları ve kütüphaneleri olan sadece C ++ olduğu için Ardunio'da sınıf yazamaz mısın? Eğer öyleyse, her değişken global değildir. Ve C'de bile, genellikle değişkenleri (belki de yapılar içinde) global değişkenlere sahip olmak yerine işlevlere dönüştürmeyi tercih etmek en iyi yöntem olarak kabul edilir. Küçük bir program için daha az uygun olabilir, ancak program büyüdükçe ve karmaşıklaştıkça genellikle işe yaramaz.
Muzer

11
@LookAlterno: " Kaçınıyorum " ve "yapamazsın" çok farklı şeylerdir.
Monica ile Hafiflik Yarışları

2
Aslında, bazı gömülü programcılar yerel değişkenleri yasaklar ve bunun yerine genel değişkenler (veya işlev kapsamındaki statik değişkenler) gerektirir. Programlar küçük olduğunda basit bir durum makinesi olarak analiz etmeyi kolaylaştırabilir; çünkü değişkenler birbirlerinin üzerine asla yazmazlar (yani: yığın ve yığın tahsis edilen değişkenleri yaptıkları gibi).
Rob,

1
Statik ve global değişkenler, derleme zamanında hafıza tüketimini bilme avantajını sunar. Çok sınırlı hafızaya sahip bir arduino ile bu bir avantaj olabilir. Bir aceminin mevcut hafızayı tüketmesi ve takip edilemeyen arızalar yaşaması oldukça kolaydır.
antipattern

Yanıtlar:


33

Kendi başlarına kötü değillerdir , ancak onları kullanmak için iyi bir neden olmayan yerlerde aşırı kullanma eğilimindedirler.

Global değişkenlerin avantajlı olduğu birçok zaman vardır. Özellikle, bir Arduino programlama, kaputun altında, bir PC'nin programlanmasından oldukça farklıdır.

Küresel değişkenlere en büyük fayda statik tahsisattır. Özellikle sınıf örnekleri gibi büyük ve karmaşık değişkenlerle. newKaynak yetersizliği nedeniyle dinamik dağıtım ( vb . Kullanımı ) kaşlarını çattı.

Ayrıca normal bir C programında yaptığınız gibi tek bir çağrı ağacı almazsınız ( main()diğer işlevleri çağıran tek işlev) - bunun yerine etkili bir şekilde iki ayrı ağacı alırsınız ( setup()çağrı işlevleri, sonra loop()çağrı işlevleri), bu da bazen global değişkenlerin Her iki kullanmak istiyorsanız tek yolu, yani hedefinize (ulaşmak için setup()veloop() ).

Yani hayır, onlar kötü değiller ve bir Arduino'da bir PC'dekinden daha fazla ve daha iyi kullanıyorlar.


Tamam, sadece kullandığım bir şeyse loop()(veya adı verilen birden fazla işlevde loop())? Onları başlangıçta tanımlamaktan farklı bir şekilde kurmak daha iyi olur mu?
ATE-ENGE

1
Bu durumda, muhtemelen bunları loop () olarak tanımlayacağım (belki de staticyinelemeler boyunca değerlerini korumak için onlara ihtiyacım varmış gibi) ve onları çağrı zincirinin altındaki işlev parametrelerinden geçirirdim.
Majenko

2
Bu tek yönlü ya da bir referans olarak geçmesi: void foo(int &var) { var = 4; }ve foo(n);- nhemen 4'tür
Majenko

1
Sınıflar yığınla tahsis edilebilir. Kuşkusuz, yığına büyük örnekler koymak iyi değil, ama yine de.
JAB

1
@JAB İstenilen her şey tahsis edilebilir.
Majenko

18

Gerçek kodunuzu görmeden kesin bir cevap vermek çok zordur.

Genel değişkenler kötü değildir ve genellikle çok fazla donanım erişimi yaptığınız gömülü bir ortamda anlaşılırlar. Yalnızca dört UARTS, yalnızca bir I2C bağlantı noktası vb. Var. Bu nedenle, belirli donanım kaynaklarına bağlı değişkenler için küresel kullanmak mantıklıdır. Ve gerçekten de, Arduino çekirdek kütüphane bunu yapmaz: Serial, Serial1vb genel değişkenlerdir. Ayrıca, programın global durumunu temsil eden bir değişken tipik olarak globaldir.

Şu anda yaklaşık 1100 satır [kod] için 46 küreselim var. Bu iyi bir oran mı [...]

Sayılarla ilgili değil. Kendinize sormanız gereken doğru soru, bu küresellerin her biri için, küresel kapsamda olmasının mantıklı olup olmadığıdır.

Yine de, 46 küresel bana biraz yüksek görünüyor. Bunların bazıları sabit değerler içeriyorsa, bunları şu şekilde nitelendirin const: derleyici genellikle depolama alanlarını optimize eder. Bu değişkenlerden herhangi biri yalnızca tek bir işlev içinde kullanılıyorsa, onu yerel yapın. Değerinin, işlev çağrıları arasında kalmasını istiyorsanız, değeri olarak nitelendirin static. Ayrıca değişkenleri bir sınıf içinde gruplayarak ve bu sınıfın bir global örneğini alarak “görülebilir” globals sayısını azaltabilirsiniz. Ancak bunu sadece bir şeyler koymak mantıklı olduğunda yapın. GlobalStuffSadece bir global değişkene sahip olmak adına büyük bir sınıf oluşturmak, kodunuzu daha net hale getirmenize yardımcı olmaz.


1
Tamam! staticSadece bir işlevde kullanılan bir değişkeni olup olmadığını bilmiyordum , ancak bir fonksiyonun çağrıldığı her zaman (gibi var=millis()) yeni bir değer alıyordum , bunu yapmalı mıyım static?
ATE-ENGE

3
Hayır static, yalnızca değeri korumanız gerektiğinde içindir. İşlevde yaptığınız ilk şey, değişkenin değerini geçerli zamana ayarlanmışsa, eski değeri tutmaya gerek yoktur. Tüm bu statik olan bu durumda yapar hafıza boşa.
Andrew,

Bu, her iki noktayı da genel olarak lehine ve şüphecilikleriyle ifade eden çok yönlü bir cevaptır. +1
underscore_d

6

Global değişkenlerle ilgili ana sorun kod bakımıdır. Bir kod satırı okurken, parametre olarak iletilen veya yerel olarak bildirilen değişkenlerin bildirimini bulmak kolaydır. Küresel değişkenlerin bildirimini bulmak o kadar kolay değildir (genellikle gerektirir ve IDE).

Çok sayıda genel değişkeniniz olduğunda (40 zaten çoktur), çok uzun olmayan açık bir isme sahip olmak zorlaşır. Ad alanını kullanmak, global değişkenlerin rolünü netleştirmenin bir yoludur.

C'deki ad alanlarını taklit etmenin kötü bir yolu:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

İntel veya arm işlemcide, global değişkenlere erişim diğer değişkenlerden daha yavaştır. Muhtemelen Arduino'nun tam tersi.


2
AVR'de globaller RAM'de bulunurken, statik olmayan çoğu yerel CPU işlemcilere ayrılır ve bu da erişimini daha hızlı hale getirir.
Edgar Bonet

1
Sorun, gerçekte beyanı bulmak değil; Kodu hızlı bir şekilde anlayabilmek önemli ancak nihayetinde ne yaptığına ikincildir - ve asıl sorun, kodunuzun bilinmeyen bir kısmının bıraktığınız bir nesneyle garip şeyler yapmak için global bildirimi kullanabildiğini bulmaktır. herkesin istediği şeyi yapması için açık.
underscore_d

5

Bir PC için programlama yaparken bunları kullanmamama rağmen, Arduino için bazı avantajları var. Çoğu zaten söylenmişse:

  • Dinamik bellek kullanımı yok (bir Arduino'nun sınırlı yığın alanında boşluklar yaratmak)
  • Her yerde mevcut, bu yüzden onları argüman olarak aktarmaya gerek yok (ki bu maliyet alanını arttırır)

Ayrıca, bazı durumlarda, özellikle performans açısından, gerektiğinde öğeler oluşturmak yerine genel değişkenleri kullanmak iyi olabilir:

  • Yığın boşluktaki boşlukları azaltmak için
  • Dinamik olarak hafıza ayırmak ve / veya boş hafıza açmak çok zaman alabilir.
  • Değişkenler, birçok nedenden dolayı, örneğin bir kez SD için bir tampon olarak ve daha sonra geçici bir dize tamponu olarak kullanılan global bir listedeki öğelerin bir listesi gibi yeniden kullanılabilir.

5

Her şeyde olduğu gibi (gerçekten kötülükten uzak olanların dışında) küresellerin kendi yerleri vardır.

Örn: her yerde yazabilmeniz için gereken bir hata ayıklama seri bağlantı noktasına veya bir günlük dosyasına sahipseniz, bunu global hale getirmek genellikle mantıklı olur. Benzer şekilde, kritik bir sistem durumu bilgisine sahipseniz, bunu global hale getirmek genellikle en kolay çözümdür. Programdaki her bir işleve geçmeniz gereken bir değere sahip olmanın bir anlamı yoktur.

Diğerlerinin dediği gibi, 46 sadece 1000 kod satırından fazlası için çok fazla görünüyor ama ne yaptığınızla ilgili detayları bilmeden bunları kullanıp kullanmadığınızı söylemek zor.

Ancak her küresel için kendinize birkaç önemli soru sorun:

Ad açık ve belirli mi, bu nedenle yanlışlıkla aynı adı başka bir yerde kullanmayı denemem mi? Adı değiştirmediyse.

Bunun hiç değişmesi gerekiyor mu? Olmazsa bir const yapmayı düşünün.

Bunun her yerde görünmesi gerekiyor mu, yoksa değer işlev çağrıları arasında korunacak şekilde yalnızca küresel mi? Bu nedenle, işlev için yerel yapmayı ve statik anahtar sözcüğünü kullanmayı düşünün.

Dikkatli olmadığımda bir kod tarafından değiştirilirse işler gerçekten kötü bir şekilde batırır mı? Örneğin, iki ilişkili değişkeniniz varsa, genel olan isim ve kimlik numaralarını söyleyin (bu, neredeyse her yerde bazı bilgilere ihtiyaç duyduğunuzda global kullanımına ilişkin önceki nota bakın), o zaman bunlardan biri diğer kötü şeyler olmadan değişebilirse. Evet, sadece dikkatli olabilirsiniz ama bazen biraz dikkatli olmanız iyi olur. örneğin, onları farklı bir .c dosyasına yerleştirin ve sonra ikisini de aynı anda ayarlayan ve bunları okumanıza izin veren işlevleri tanımlayın. Daha sonra sadece ilişkili başlık dosyasına işlevleri dahil edersiniz, bu şekilde kodunuzun geri kalanı yalnızca tanımlanmış işlevler aracılığıyla değişkenlere erişebilir ve bu yüzden işleri karıştırmaz.

- güncelleme - Sadece genel kodlama yerine Arduino'ya özgü en iyi uygulama hakkında sorular sorduğunuzu fark ettim ve bu daha genel bir kodlama yanıtı. Ama dürüst olmak gerekirse, çok fazla fark yok, iyi uygulama iyi uygulama. startup()Ve loop()sen kullanımına sahip olduğu Arduino araçlarının yapısı bazı durumlarda diğer platformlarda daha bir az şey Globaller ama bu gerçekten her zaman olursa olsun platformun sınırlamaları dahilinde yapabileceği en iyi hedefliyor sonunda, pek değişmez neyi platform.


Arduinos hakkında hiçbir şey bilmiyorum ama çok fazla masaüstü ve sunucu geliştirme yapıyorum. gotoS için kabul edilebilir bir kullanım (IMHO) var ve bu iç içe döngülerden kopuyor, alternatiflerden daha temiz ve kolay anlaşılıyor.
Israr

gotoKötü olduğunu düşünüyorsanız , Linux kodunu kontrol edin. Muhtemelen, bu şekilde try...catchkullanıldığında bloklar kadar kötüler .
Dmitry Grigoryev

5

Onlar kötülük mü? Olabilir. Globals ile ilgili sorun, herhangi bir zamanda, herhangi bir zamanda, herhangi bir fonksiyon ya da kod parçası tarafından, kısıtlama olmaksızın, erişilebilmesi ve değiştirilebilmesidir. Bu, izini sürmek ve açıklamak zor diyelim, diyelim. Bu nedenle, mümkünse sıfıra geri getirerek küresel miktarının en aza indirilmesi arzu edilir.

Kaçınılabilirler mi? Neredeyse her zaman evet. Arduino'nun sorunu, sizi setup()ve sizi kabul ettikleri bu iki işlev yaklaşımına zorlamalarıdır loop(). Bu özel durumda, bu iki fonksiyonun (muhtemelen main()) arayan işlevinin kapsamına erişiminiz yoktur . Elinizde olsaydı, kendinizi tüm dünyadan kurtarır ve onun yerine yerliler kullanırsınız.

Aşağıdakileri hayal edin:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Bu muhtemelen bir Arduino programının temel işlevinin neye benzediğidir. Değişkenler hem de ihtiyaç setup()ve loop()fonksiyon daha sonra tercihen kapsamında iç ilan edilirdimain() fonksiyonu yerine küresel kapsam. Daha sonra argüman olarak geçirerek (gerekirse işaretçiler kullanarak) diğer iki fonksiyona erişilebilir hale getirilebilirler.

Örneğin:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Bu durumda, her iki fonksiyonun imzasını da değiştirmeniz gerektiğini unutmayın.

Bu mümkün veya arzu edilmeyebileceği için, zorlanan program yapısını değiştirmeden bir çok Arduino programından globları çıkarmanın tek bir yolunu görüyorum.

Doğru hatırlıyorsam, C + yerine Arduino için programlama yaparken C ++ 'ı mükemmel şekilde kullanabiliyorsunuz (henüz) OOP (Nesneye Yönelik Programlama) hakkında bilginiz yoksa veya C ++ , biraz alıştırabilir ve bazı okuma.

Teklifim bir Program sınıfı oluşturmak ve bu sınıfın tek bir küresel örneğini oluşturmak olacaktır. Bir sınıf, nesnelerin planı olarak düşünülmelidir.

Aşağıdaki örnek programı göz önünde bulundurun:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, kendimizi hemen hemen tüm dünyalardan kurtardık. Uygulama mantığınızı eklemeye başlayacağınız işlevler Program::setup()ve Program::loop()işlevleri olacaktır. Bu işlevler örneği belirli üye değişkenlerine erişebilir myFirstSampleVariableve mySecondSampleVariablegeleneksel oysa setup()ve loop()bu değişkenler sınıf olarak gizli işaretlenmiş olarak erişimi olmayan fonksiyonlar. Bu kavram veri kapsülleme olarak adlandırılır. veya veri gizleme .

Size OOP ve / veya C ++ 'ların öğretilmesi, bu sorunun cevabının kapsamı dışında kaldığı için burada duracağım.

Özetlemek gerekirse: kürelerden kaçınılmalı ve küreler miktarını büyük ölçüde azaltmak neredeyse her zaman mümkün. Ayrıca Arduino için programlama yaparken.

En önemlisi, cevabımın sizin için biraz faydalı olduğunu umuyorum :)


İsterseniz eskizde kendi main'inizi () tanımlayabilirsiniz. Bu hisse senedi biri gibi görünüyor: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234

Bir singleton etkili bir şekilde küresel, sadece kafa karıştırıcı bir şekilde giyinmiş. Aynı olumsuz tarafları vardır.
Patstew

@patstew Nasıl aynı dezavantajlara sahip olduğunu düşündüğünüzü açıklar mısınız? Kanımca veri kapsüllemesi kullanabildiğinize göre bence değil.
Arjen

@ per1234 Teşekkürler! Kesinlikle Arduino uzmanı değilim, ancak ilk önerimin de o zaman işe yarayacağını düşünüyorum.
Arjen

2
Eh, hala programın herhangi bir yerine ulaşılabilen küresel bir durumdur, Program::instance().setup()bunun yerine onun yerine erişebilirsiniz globalProgram.setup(). İlgili global değişkenleri bir sınıfa / yapı / isim alanına koymak, özellikle de birkaç ilgili fonksiyona ihtiyaç duyuyorsa faydalı olabilir, ancak singleton modeli bir şey eklemez. Başka bir deyişle static Program p;, küresel depolamaya static Program& instance()sahip ve aynı şekilde olduğu gibi küresel erişime sahip Program globalProgram;.
Patstew

4

Global değişkenler asla kötü değildir . Onlara karşı genel bir kural, daha iyi kararlar alabilmek için tecrübe kazanmanız için yeterince uzun süre hayatta kalmanıza izin veren bir engeldir.

Global bir değişken ne demek, bir şeyin yalnızca birinin olduğuna dair içsel bir varsayımdır (birden fazla şey içerebilecek bir global dizi veya haritadan bahsetmiş olmamızın bir önemi yoktur, yine de sadece olduğu varsayımını içerir) bir tür liste veya harita ve birden değil, bağımsız olanlar).

Yani bir küreselden faydalanmadan önce kendinize sormak istersiniz: Bu şeyden daha fazlasını kullanmak isteyeceğim düşünülebilir mi? Çizgide doğru olduğu ortaya çıkarsa, bu şeyi genelleştirmek için kodu değiştirmeniz gerekecektir ve muhtemelen kodunuzun diğer bölümlerinin bu benzersiz varsayımlara bağlı olduğu yolunda öğrenirsiniz. Onları da düzeltmek zorunda kalacağım ve süreç sıkıcı ve hataya açık hale geliyor. “Globals kullanmayın” öğretilir çünkü genel olarak globaller baştan kaçınmak için oldukça küçük bir maliyettir ve daha sonra büyük bir maliyet ödemek zorunda olma potansiyelinden kaçınır.

Ancak, küresellerin sağladığı basitleştirici varsayımlar, kodunuzu daha küçük, daha hızlı hale getirir ve daha az bellek kullanır, çünkü kullandığı şeyin nosyonunu dolaşmak zorunda değildir, dolaylı olarak yapmak zorunda değildir. İstenen şeyin belki de mevcut olmaması ihtimalini göz önünde bulundurun, vb. Yerleşik olarak, bir PC'de olduğundan daha büyük kod boyutuna ve / veya CPU zamanına ve / veya belleğine sahip olma ihtimaliniz daha düşüktür, bu nedenle bu tasarruf önemli olabilir. Ayrıca birçok gömülü uygulama gereksinimlerinde daha fazla sağlamlığa sahiptir - çipinizin yalnızca belirli bir çevre biriminden birine sahip olduğunu bilirsiniz , kullanıcı bir başkasını bir USB bağlantı noktasına veya başka bir şeye takamaz.

Benzersiz görünen bir şeyden daha fazlasını istemek için başka bir yaygın neden test yapmaktır - iki bileşen arasındaki etkileşimi test etmek, bir bileşenin test örneğini bir işleve geçirebildiğiniz zaman daha kolaydır, oysa global bir bileşenin davranışını değiştirmeye çalışmaktır. daha zor bir teklif. Ancak gömülü dünyada yapılan testler başka yerlerden çok farklı olma eğilimindedir, bu yüzden bu sizin için geçerli olmayabilir. Bildiğim kadarıyla Arduino'nun hiçbir şekilde test kültürü yok.

Öyleyse devam edin ve değerli göründüğü zaman küreler kullanın. Polis polisi gelip seni almayacak. Yanlış seçimin senin için yolun üstünde daha fazla işe neden olabileceğini bil, bu yüzden emin olmazsan ...


0

Arduino'da Küresel Değişkenler Kötü mü?

Global değişkenler dahil hiçbir şey içsel olarak kötü değildir. Bunu "gerekli bir kötülük" olarak nitelendiriyordum - hayatınızı çok daha kolaylaştırabilir ancak dikkatle yaklaşılması gerekir.

Ayrıca, küre sayısını daha da azaltmak için hangi uygulamaları kullanabilirim?

Global değişkenlerinize erişmek için sarmalayıcı işlevlerini kullanın. bu yüzden en azından kapsam perspektifinden yönetiyorsunuz.


3
Genel değişkenlere erişmek için sarmalayıcı işlevlerini kullanırsanız, değişkenlerinizi bu işlevlerin içine de yerleştirebilirsiniz.
Dmitry Grigoryev
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.