C'de sınırlı girdiyle fgets veya scanf kullanmalı mıyım?

0

Soru

Kullanmalı mıyım fgets veya biçimlendirilmiş scanf hoşlanmak scanf("%10s", foo).

O hariç scanf çözülebilen ve scanset ile daha fazla şey yapabilen boş karakterleri okumaz, o zaman neden kullanmalıyım fgets yerine scanf?

Herhangi bir yardım takdir edilecektir.


Düzenlemek

Sormak istediğim bir şey daha var: kullandığımızda bile fgets kullanıcı sınırdan daha fazla karakter girerse ne olur (çok fazla karakter kastediyorum), arabellek taşmasına neden olur mu? O zaman bununla nasıl başa çıkılır?

c fgets input scanf
2021-11-23 13:53:00
4

En iyi cevabı

5

Çoğu işletim sisteminde, kullanıcı girişi varsayılan olarak satır tabanlıdır. Bunun bir nedeni, kullanıcının girişi programa göndermeden önce girişi düzeltmek için backspace tuşuna basmasına izin vermektir.

Satır tabanlı kullanıcı girişi için, bir programın bir seferde bir satır girişi okuması anlamlı ve sezgiseldir. Bu işlev nedirfgets yapar (arabelleğin tüm giriş satırını depolayacak kadar büyük olması şartıyla).

İşlev scanf öte yandan, normalde bir seferde bir satır giriş okumaz. Örneğin, kullandığınızda %s veya %d dönüşüm biçimi belirticisi ile scanf, tüm bir girdi satırını tüketmez. Bunun yerine, yalnızca dönüşüm biçimi belirticisiyle eşleştiği kadar girdi tüketir. Bu, satırın sonundaki yeni satır karakterinin normalde tüketilmeyeceği anlamına gelir (bu, programlama hatalarına kolayca yol açabilir). Ayrıca, scanf ile aradı %d dönüşüm biçimi belirticisi, aşağıdaki gibi girdileri dikkate alacaktır 6sldf23dsfh2 numara için geçerli girdi olarak 6, ama başka aramalar scanf satırın geri kalanını giriş akışından atmadığınız sürece, aynı belirtici ile başarısız olur.

Bu davranış scanf karşı sezgiseldir, oysa davranış fgets satır tabanlı kullanıcı girişi ile uğraşırken sezgiseldir.

Kullandıktan sonra fgets yapabilirsiniz işlevini kullanın sscanf dizede, tek bir satırın içeriğini ayrıştırmak için. Bu, tarama kümelerini kullanmaya devam etmenizi sağlar. Veya satırı başka yollarla ayrıştırabilirsiniz. Her iki durumda da, kullandığınız sürece fgets yerine scanf girişi okumak için, satır tabanlı kullanıcı girişi ile başa çıkmanın doğal ve sezgisel yolu olan bir seferde bir satır girişi kullanacaksınız.

Ne zaman kullanırız fgets kullanıcı sınırdan daha fazla karakter girerse ne olur (çok fazla karakter kastediyorum), arabellek taşmasına neden olur mu? O zaman bununla nasıl başa çıkılır?

Kullanıcı, ikinci tarafından belirtildiği şekilde arabelleğe sığandan daha fazla karakter girerse fgets işlev argümanı, o zaman arabelleği taşmaz. Bunun yerine, yalnızca giriş akışından arabelleğe sığacak kadar karakter çıkarır. Dizenin bir satırsonu karakteri içerip içermediğini kontrol ederek tüm satırın okunup okunmadığını belirleyebilirsiniz '\n' sonunda.

2021-11-23 15:13:39

Çok teşekkür ederim, cevabınız bana gerçekten yardımcı oldu.
Becker

Fgets () davranışı yalnızca beklenenden uzun olmayan girdiler için sezgiseldir.
supercat
2

Bu yaygın olarak tartışılan bir konudur, görüş dolu ama daha az ilginç değildir. Bu sitede benzer sorulara daha önce cevap vermiş olanların büyük bir çoğunluğunun şu tarafa düştüğünü gözlemledim: fgets(). Ben de onlardan biriyim. Buluyorum fgets() kullanıcı girişi için kullanmaktan çok daha iyi olmak scanf() birkaç istisna dışında. scanf() olduğunu kabul ederek birçok gibi alt-optimal yöntem kullanım için kullanıcı girişi. Örneğin

"...size başarılı olup olmadığını veya başarısız olup olmadığını söyleyecektir, ancak size yalnızca nerede başarısız olduğunu ve nasıl veya neden olduğunu söyleyemez. Sen herhangi bir hata kurtarma yapmak için çok az fırsatınız var."
(jamesdlin). Ancak dengeye teşebbüs etmek adına, bu tartışmayı gerekçe göstererek başlayacağız.

Gelen kullanıcı girişi için stdin klavye girişi, fgets() daha iyi bir seçim olacaktır. Dönüşüm denenmeden önce okuduğu dizenin tamamen doğrulanabilmesi çok daha bağışlayıcıdır

Bir scanf(): fscanf() formunu kullanmanın birkaç zamanından biri, girişi çok kontrollü bir kaynaktan dönüştürürken, yani tahmin edilebilir alanları tekrarlayan kesin olarak biçimlendirilmiş bir dosyayı okurken kullanmak iyi olabilir.

Daha fazla tartışma için, ikisinin bu karşılaştırması, her ikisinin de ek avantajlarını ve dezavantajlarını vurgulamaktadır.

Edit: Adres OP ek soru hakkında taşması:

"Sormak istediğim bir şey daha var: fgets kullandığımızda bile, kullanıcı karakterleri sınırdan daha fazla girerse ne olur (çok fazla demek istiyorum karakterler), arabellek taşmasına neden olur mu? O zaman nasıl başa çıkılır mi?"

[fgets ()] (https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm parametrelerini düzgün bir şekilde kullanarak arabellek taşmasını önlemek için güzel bir şekilde tasarlanmıştır, örn.:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Bu, arabellek boyutundan daha büyük girdilerin işlenmesini engeller ve böylece taşmayı önler.

hatta kullanarak scanf()arabellek taşmasını önlemek oldukça basittir: Biçim dizesinde bir genişlik belirticisi kullanın. Örneğin girişi okumak ve giriş boyutunu kullanıcıdan maksimum 100 karakterle sınırlamak istiyorsanız, kod aşağıdakileri içerir:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

Ancak, sayılarla taşma kullanmak o kadar da hoş değil scanf(). Göstermek için, çalışma başına bir yorumda belirtilen iki değeri girerek bu basit kodu kullanın:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

İkinci değer için sistemim aşağıdakileri atar:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Burada bir dizede okumanız, ardından aşağıdakilerden birini kullanarak bir dizeden sayıya dönüşüm izlemeniz gerekir strto_() fonksiyonlar: strtol (), strtod (),...). Her ikisi de çalışma zamanı uyarısı veya hatasına neden olmadan önce taşma olup olmadığını sınama yeteneğini içerir. Bunu kullanarak unutmayın atoi(), atod() taşmaya karşı da koruma sağlamaz.

2021-11-23 14:10:20

Teşekkürler, cevabınız için gerçekten minnettarım.
Becker

"Fikir dolu" bir soru mu? Saygıyla katılmıyorum. Bu bir fikir meselesi değil scanf neredeyse tamamen işe yaramaz, intro-to-C programlarındaki tek, basit girdileri okumak için en iyi ihtimalle iyidir, ancak uzaktan karmaşık bir şey yapmak oldukça zordur-bunlar bariz gerçeklerdir! :-)
Steve Summit
1

Şimdiye kadar, buradaki tüm cevaplar inceliklerini sundu scanf ve fgets ancak bahsetmeye değer olduğuna inandığım şey, bu işlevlerin her ikisinin de mevcut C standardında kullanımdan kaldırılmış olmasıdır. Scanf özellikle tehlikelidir, çünkü arabellek taşmalarıyla ilgili her türlü güvenlik sorunu vardır. fgets bu kadar sorunlu değil, ama benim tecrübelerime göre, pratikte biraz beceriksiz ve çok kullanışlı olma eğilimindedir.

Gerçek şu ki, çoğu zaman kullanıcı girdisinin ne kadar süreceğini gerçekten bilmiyorsunuz. Umarım bu yeterince büyük bir tampon olur, ancak bu gerçekten ellegant değildir. Bunun yerine, sık sık yapmak istediğiniz şey, kullanıcı girdisinin sağlayacağı her şeyi depolayacak kadar büyüyecek dinamik arabelleğe sahip olmaktır. Ve bu olduğunda getline işlev devreye girer. \N ile karşılaşılana kadar kullanıcıdan herhangi bir sayıda karakteri okumak için kullanılır. Esasen, tüm satırı bir dize olarak belleğinize yükler.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Bu işlev, ilk bağımsız değişken olarak dinamik olarak ayrılmış bir dizeye bir işaretçi ve ikinci bağımsız değişken olarak ayrılmış arabelleğin boyutuna ve üçüncü bağımsız değişken olarak akışa bir işaretçi alır. (esasen yerleştireceksiniz stdin komut satırı girişi için var). Ve sonunda \n de dahil olmak üzere okunan karakter sayısını döndürür, ancak sonlandırıcı null değerini döndürmez.

Burada, bu işlevin örnek kullanımını görebilirsiniz:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Tabii ki, bu işlevi kullanmak, C'de işaretçiler ve dinamik bellek hakkında temel bilgi gerektirir, ancak biraz daha karmaşık doğası getline sağlanan güvenlik ve esneklik nedeniyle kesinlikle buna değer.

Bu işlev ve C'de bulunan diğer giriş işlevleri hakkında daha fazla bilgiyi bu web sitesinde okuyabilirsiniz: https://www.studymite.com/blog/strings-in-c C girdisinin inceliklerini oldukça iyi özetlediğine inanıyorum.

2021-11-23 19:18:00

Tavsiyeniz ve bağlantınız için teşekkürler, bana çok yardımcı oluyor.
Becker
1

Örneğin, aşağıdaki gibi bildirilen bir karakter diziniz varsa

char s[100];

ve gömülü boşluklar içeren bir dizeyi okumak istiyorsanız, ikisini de kullanabilirsiniz scanf aşağıdaki şekilde:

scanf( "%99[^\n]", s );

veya fgets hoşlanmak:

fgets( s, sizeof( s ), stdin );

Bu iki çağrı arasındaki fark, scanf yeni satır karakteri okunmuyor '\n' giriş arabelleğinden. Karşın fgets yeni satır karakterini okur '\n' karakter dizisinde yeterli alan varsa.

Yeni satır karakterini kaldırmak için '\n' kullandıktan sonra karakter dizisinde depolanan fgets örneğin yazabilirsiniz:

s[ strcspn( s, "\n" ) ] = '\0';

Giriş dizesinde 99'dan fazla karakter varsa, her iki çağrı da yalnızca 99 karakteri okur ve karakter dizisini sonlandırma sıfır karakteriyle ekler '\0'. Kalan tüm karakterler hala giriş arabelleğinde olacaktır.

Bir sorun var fgets. Örneğin daha önce fgets orada kullanılır scanf örneğin:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

ve kullanıcı girişi:

10
Hello World

sonra çağrı fgets sadece yeni satır karakterini okuyacak '\n' bu, aramada tamsayı değeri olduğunda Enter tuşuna basıldıktan sonra arabellekte saklanır. scanf okundu.

Bu durumda, yeni satır karakterini kaldıracak bir kod yazmanız gerekir '\n' aramadan önce fgets.

Bunu örneğin şu şekilde yapabilirsiniz:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Eğer kullanıyorsanız scanf sonra böyle bir durumda yazabilirsiniz:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

Diğer dillerde

Bu sayfa diğer dillerde

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................