C Language
आम नुकसान
खोज…
परिचय
इस खंड में कुछ सामान्य गलतियों पर चर्चा की गई है जो एक सी प्रोग्रामर को पता होनी चाहिए और इसे बनाने से बचना चाहिए। कुछ अप्रत्याशित समस्याओं और उनके कारणों के बारे में अधिक जानने के लिए, कृपया अपरिभाषित व्यवहार देखें
अंकगणित संचालन में हस्ताक्षरित और अहस्ताक्षरित पूर्णांक को मिलाना
आमतौर पर अंकगणित संचालन में signed
और unsigned
पूर्णांक को मिलाना अच्छा विचार नहीं है। उदाहरण के लिए, निम्नलिखित उदाहरण का आउटपुट क्या होगा?
#include <stdio.h>
int main(void)
{
unsigned int a = 1000;
signed int b = -1;
if (a > b) puts("a is more than b");
else puts("a is less or equal than b");
return 0;
}
चूँकि 1000 -1 से अधिक है, आप उम्मीद करेंगे कि आउटपुट a is more than b
, हालाँकि ऐसा नहीं होगा।
विभिन्न अभिन्न प्रकारों के बीच अंकगणितीय संचालन तथाकथित सामान्य अंकगणितीय रूपांतरण (भाषा विनिर्देश, 6.3.1.8 देखें) द्वारा परिभाषित एक सामान्य प्रकार के भीतर किया जाता है।
इस मामले में "सामान्य प्रकार" unsigned int
, क्योंकि, जैसा कि सामान्य अंकगणितीय रूपांतरणों में कहा गया है,
714 अन्यथा, यदि संकलित पूर्णांक प्रकार वाले ऑपरेंड में अन्य ऑपरेंड के प्रकार के रैंक के बराबर या उससे अधिक रैंक है, तो हस्ताक्षर किए गए पूर्णांक प्रकार के साथ ऑपरेटर को बिना पूर्णांक प्रकार वाले ऑपरेंड के प्रकार में बदल दिया जाता है।
इसका मतलब है कि int
संकार्य b
के लिए परिवर्तित हो जाएगी unsigned int
तुलना से पहले।
जब -1 को एक unsigned int
में बदल दिया जाता है तो परिणाम अधिकतम संभव unsigned int
मान होता है, जो 1000 से अधिक होता है, जिसका अर्थ है कि a > b
गलत है।
तुलना करते समय गलती से == के बजाय
=
ऑपरेटर का उपयोग असाइनमेंट के लिए किया जाता है।
तुलना के लिए ==
ऑपरेटर का उपयोग किया जाता है।
एक को सावधान रहना चाहिए कि दोनों को न मिलाएं। कभी-कभी कोई गलती से लिख देता है
/* assign y to x */
if (x = y) {
/* logic */
}
जब वास्तव में क्या चाहिए था:
/* compare if x is equal to y */
if (x == y) {
/* logic */
}
भूतपूर्व का मान x को निर्दिष्ट करता है और जाँचता है कि यदि मूल्य शून्य है, तो तुलना करने के बजाय, जो इसके बराबर है:
if ((x = y) != 0) {
/* logic */
}
ऐसे समय होते हैं जब किसी असाइनमेंट के परिणाम का परीक्षण किया जाता है और आमतौर पर इसका उपयोग किया जाता है, क्योंकि यह डुप्लिकेट कोड होने और पहली बार विशेष रूप से इलाज करने से बचता है। तुलना
while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
switch (c) {
...
}
}
बनाम
c = getopt_long(argc, argv, short_options, long_options, &option_index);
while (c != -1) {
switch (c) {
...
}
c = getopt_long(argc, argv, short_options, long_options, &option_index);
}
आधुनिक कंपाइलर इस पैटर्न को पहचानेंगे और जब असाइनमेंट को ऊपर की तरह कोष्ठक के अंदर है तो चेतावनी न दें, लेकिन अन्य उपयोगों के लिए चेतावनी दे सकते हैं। उदाहरण के लिए:
if (x = y) /* warning */
if ((x = y)) /* no warning */
if ((x = y) != 0) /* no warning; explicit */
कुछ प्रोग्रामर ऑपरेटर की बाईं ओर स्थिर रखने की रणनीति का उपयोग करते हैं (आमतौर पर योडा स्थितियां कहा जाता है)। क्योंकि स्थिरांक व्याप्त हैं, इस शैली की स्थिति गलत संकलक का उपयोग करने पर संकलक को फेंकने का कारण बनेगी।
if (5 = y) /* Error */
if (5 == y) /* No error */
हालांकि, यह कोड की पठनीयता को गंभीर रूप से कम कर देता है और इसे आवश्यक नहीं माना जाता है यदि प्रोग्रामर अच्छी सी कोडिंग प्रथाओं का पालन करता है, और दो चर की तुलना करते समय मदद नहीं करता है तो यह एक सार्वभौमिक समाधान नहीं है। इसके अलावा, कई आधुनिक संकलक चेतावनी दे सकते हैं जब कोड को योदा स्थितियों के साथ लिखा जाता है।
अर्धविरामों का अनुचित उपयोग
अर्धविराम से सावधान रहें। उदाहरण के बाद
if (x > a);
a = x;
वास्तव में इसका मतलब है:
if (x > a) {}
a = x;
जो साधन x
को सौंपा जाएगा a
किसी भी मामले है, जो नहीं हो सकता है क्या आप मूल रूप से चाहता था में।
कभी-कभी, अर्धविराम गायब होने से भी किसी का ध्यान नहीं जाएगा:
if (i < 0)
return
day = date[0];
hour = date[1];
minute = date[2];
वापसी के पीछे अर्धविराम याद किया जाता है, इसलिए दिन = तारीख [0] वापस कर दी जाएगी।
इससे बचने और इसी तरह की समस्याओं के लिए एक तकनीक हमेशा मल्टी-लाइन सशर्त और छोरों पर ब्रेसिज़ का उपयोग करना है। उदाहरण के लिए:
if (x > a) {
a = x;
}
\ 0 के लिए एक अतिरिक्त बाइट आवंटित करना भूल गया
जब आप एक स्ट्रिंग को एक malloc
एड बफर में कॉपी कर रहे हैं, तो हमेशा 1 को strlen
जोड़ना याद रखें।
char *dest = malloc(strlen(src)); /* WRONG */
char *dest = malloc(strlen(src) + 1); /* RIGHT */
strcpy(dest, src);
इसका कारण यह है कि strlen
में लंबाई में अनुगामी \0
शामिल नहीं है। यदि आप ले WRONG
, (जैसा कि ऊपर दिखाया गया है) बुला पर दृष्टिकोण strcpy
, अपने कार्यक्रम अपरिभाषित व्यवहार आह्वान करेंगे।
यह उन स्थितियों पर भी लागू होता है जब आप stdin
या किसी अन्य स्रोत से ज्ञात अधिकतम लंबाई की एक स्ट्रिंग पढ़ रहे होते हैं। उदाहरण के लिए
#define MAX_INPUT_LEN 42
char buffer[MAX_INPUT_LEN]; /* WRONG */
char buffer[MAX_INPUT_LEN + 1]; /* RIGHT */
scanf("%42s", buffer); /* Ensure that the buffer is not overflowed */
मुक्त स्मृति (स्मृति लीक) को भूलकर
एक प्रोग्रामिंग सबसे अच्छा अभ्यास किसी भी मेमोरी को मुक्त करना है जिसे सीधे आपके स्वयं के कोड द्वारा आवंटित किया गया है, या आंतरिक या बाहरी फ़ंक्शन को कॉल करके, जैसे कि लाइब्रेरी एपीआई जैसे strdup()
। मुक्त मेमोरी में विफल रहने से मेमोरी रिसाव हो सकता है, जो कि आपके प्रोग्राम (या सिस्टम) के लिए अनुपलब्ध व्यर्थ मेमोरी में जमा हो सकता है, संभवतः क्रैश या अपरिभाषित व्यवहार के लिए अग्रणी हो सकता है। यदि रिसाव एक लूप या पुनरावर्ती फ़ंक्शन में बार-बार होने पर समस्याएँ होने की अधिक संभावना है। कार्यक्रम की विफलता का खतरा बढ़ जाता है एक लीक कार्यक्रम चलता है। कभी-कभी समस्याएं तुरंत दिखाई देती हैं; अन्य समय समस्याओं को घंटों या निरंतर संचालन के वर्षों के लिए भी नहीं देखा जाएगा। स्मृति की थकावट विफलता परिस्थितियों के आधार पर विनाशकारी हो सकती है।
निम्नलिखित अनंत लूप एक रिसाव का एक उदाहरण है जो अंततः मेमोरी getline()
को कॉल करके उपलब्ध मेमोरी लीक को getline()
कर getline()
, एक फ़ंक्शन जो कि उस मेमोरी को मुक्त किए बिना, नई मेमोरी आवंटित करता है।
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *line = NULL;
size_t size = 0;
/* The loop below leaks memory as fast as it can */
for(;;) {
getline(&line, &size, stdin); /* New memory implicitly allocated */
/* <do whatever> */
line = NULL;
}
return 0;
}
इसके विपरीत, नीचे दिया गया कोड भी getline()
फ़ंक्शन का उपयोग करता है, लेकिन इस बार, लीक से बचने के लिए आवंटित मेमोरी को सही ढंग से मुक्त किया गया है।
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *line = NULL;
size_t size = 0;
for(;;) {
if (getline(&line, &size, stdin) < 0) {
free(line);
line = NULL;
/* Handle failure such as setting flag, breaking out of loop and/or exiting */
}
/* <do whatever> */
free(line);
line = NULL;
}
return 0;
}
याददाश्त कमजोर होने के हमेशा ठोस परिणाम नहीं होते हैं और जरूरी नहीं कि यह एक कार्यात्मक समस्या हो। जबकि "सर्वोत्तम अभ्यास" रणनीतिक बिंदुओं और शर्तों पर सख्ती से मुक्त स्मृति को निर्देशित करता है, स्मृति पदचिह्न और स्मृति थकावट के कम जोखिम को कम करने के लिए, अपवाद हो सकते हैं। उदाहरण के लिए, यदि कोई कार्यक्रम अवधि और दायरे में बंधा हुआ है, तो आवंटन की विफलता के जोखिम के बारे में चिंता करने के लिए बहुत छोटा माना जा सकता है। उस मामले में, स्पष्ट सौदे को दरकिनार करके स्वीकार्य माना जा सकता है। उदाहरण के लिए, अधिकांश आधुनिक ऑपरेटिंग सिस्टम स्वचालित रूप से एक प्रोग्राम द्वारा खपत की गई सभी मेमोरी को मुफ्त कर देते हैं, जब यह समाप्त हो जाता है, चाहे वह प्रोग्राम विफलता के कारण हो, सिस्टम कॉल से exit()
, प्रक्रिया समाप्ति या main()
अंत तक पहुंचना main()
। आसन्न कार्यक्रम समाप्ति के बिंदु पर स्पष्ट रूप से मुक्त स्मृति वास्तव में बेमानी हो सकती है या एक प्रदर्शन दंड का परिचय दे सकती है।
अपर्याप्त स्मृति उपलब्ध होने पर आवंटन विफल हो सकता है, और कॉल स्टैक के उपयुक्त स्तरों के लिए असफलताओं को संभालना चाहिए। getline()
, ऊपर दिखाया गया एक दिलचस्प उपयोग-मामला है क्योंकि यह एक लाइब्रेरी फ़ंक्शन है जो न केवल मेमोरी को आवंटित करने के लिए कॉल करने वाले को मुफ्त में आवंटित करता है, बल्कि कई कारणों से विफल हो सकता है, जिनमें से सभी को ध्यान में रखा जाना चाहिए। इसलिए, C API का उपयोग करते समय, प्रलेखन (मैन पेज) को पढ़ने और त्रुटि की स्थिति और स्मृति के उपयोग पर विशेष ध्यान देना आवश्यक है, और यह ध्यान रखें कि कौन सी सॉफ़्टवेयर लेयर मुक्त की गई मेमोरी का भार वहन करती है।
एक अन्य आम मेमोरी हैंडलिंग प्रैक्टिस है जो उन पॉइंटर्स द्वारा संदर्भित मेमोरी के मुक्त होने के तुरंत बाद मेमोरी पॉइंटर्स को NULL में सेट करने के लिए है, इसलिए उन पॉइंटर्स को किसी भी समय वैधता के लिए परीक्षण किया जा सकता है (जैसे NULL / गैर-NULL के लिए चेक किया गया), क्योंकि मुक्त हुई मेमोरी कचरा डाटा (रीड ऑपरेशन), या डेटा भ्रष्टाचार (राइट ऑपरेशन) और / या प्रोग्राम क्रैश जैसी गंभीर समस्याएं हो सकती हैं। अधिकांश आधुनिक ऑपरेटिंग सिस्टमों में, मेमोरी लोकेशन 0 ( NULL
) को मुक्त करना एक NOP (जैसे कि यह हानिरहित है), जैसा कि C मानक द्वारा आवश्यक है - इसलिए NULL को पॉइंटर सेट करने से, पॉइंटर से डबल-मेमोरी मेमोरी का कोई खतरा नहीं है। free()
लिए पारित किया है free()
। ध्यान रखें कि डबल-फ्री मेमोरी में बहुत समय लगने, भ्रामक होने और विफलताओं का निदान करने में मुश्किल हो सकती है ।
बहुत नकल करना
char buf[8]; /* tiny buffer, easy to overflow */
printf("What is your name?\n");
scanf("%s", buf); /* WRONG */
scanf("%7s", buf); /* RIGHT */
यदि उपयोगकर्ता 7 वर्णों (- शून्य टर्मिनेटर के लिए - 1) से अधिक स्ट्रिंग में प्रवेश करता है, तो बफ़र buf
पीछे की मेमोरी फिर से buf
जाएगी। इससे अपरिभाषित व्यवहार होता है। दुर्भावनापूर्ण हैकर्स अक्सर रिटर्न एड्रेस को अधिलेखित करने के लिए इसका उपयोग करते हैं, और इसे हैकर के दुर्भावनापूर्ण कोड के पते में बदल देते हैं।
एक अस्थायी में realloc के वापसी मूल्य की प्रतिलिपि बनाना भूल जाते हैं
यदि realloc
विफल रहता है, तो यह NULL
देता NULL
। आप करने के लिए मूल बफर के मूल्य असाइन करते हैं realloc
की वापसी मान, और अगर यह रिटर्न NULL
है, तो मूल बफर (पुराने सूचक) खो दिया है, एक में जिसके परिणामस्वरूप स्मृति रिसाव । समाधान को एक अस्थायी सूचक में कॉपी करना है, और यदि वह अस्थायी NULL नहीं है, तो वास्तविक बफर में कॉपी करें।
char *buf, *tmp;
buf = malloc(...);
...
/* WRONG */
if ((buf = realloc(buf, 16)) == NULL)
perror("realloc");
/* RIGHT */
if ((tmp = realloc(buf, 16)) != NULL)
buf = tmp;
else
perror("realloc");
फ्लोटिंग पॉइंट संख्याओं की तुलना करना
फ्लोटिंग पॉइंट प्रकार ( float
, double
और long double
) कुछ संख्याओं का ठीक-ठीक प्रतिनिधित्व नहीं कर सकते क्योंकि उनमें परिमित परिशुद्धता होती है और वे बाइनरी प्रारूप में मूल्यों का प्रतिनिधित्व करते हैं। जैसे हम 1/3 जैसे अंशों के लिए बेस 10 में दशमलव दोहरा रहे हैं, वैसे ही ऐसे अंश हैं जो द्विआधारी में भी प्रतिनिधित्व नहीं कर सकते हैं (जैसे 1/3, लेकिन यह भी, अधिक महत्वपूर्ण बात, 1/10)। फ्लोटिंग पॉइंट मानों की सीधे तुलना न करें; इसके बजाय एक डेल्टा का उपयोग करें।
#include <float.h> // for DBL_EPSILON and FLT_EPSILON
#include <math.h> // for fabs()
int main(void)
{
double a = 0.1; // imprecise: (binary) 0.000110...
// may be false or true
if (a + a + a + a + a + a + a + a + a + a == 1.0) {
printf("10 * 0.1 is indeed 1.0. This is not guaranteed in the general case.\n");
}
// Using a small delta value.
if (fabs(a + a + a + a + a + a + a + a + a + a - 1.0) < 0.000001) {
// C99 5.2.4.2.2p8 guarantees at least 10 decimal digits
// of precision for the double type.
printf("10 * 0.1 is almost 1.0.\n");
}
return 0;
}
एक और उदाहरण:
gcc -O3 -g -I./inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition rd11.c -o rd11 -L./lib -lsoq
#include <stdio.h>
#include <math.h>
static inline double rel_diff(double a, double b)
{
return fabs(a - b) / fmax(fabs(a), fabs(b));
}
int main(void)
{
double d1 = 3.14159265358979;
double d2 = 355.0 / 113.0;
double epsilon = 1.0;
for (int i = 0; i < 10; i++)
{
if (rel_diff(d1, d2) < epsilon)
printf("%d:%.10f <=> %.10f within tolerance %.10f (rel diff %.4E)\n",
i, d1, d2, epsilon, rel_diff(d1, d2));
else
printf("%d:%.10f <=> %.10f out of tolerance %.10f (rel diff %.4E)\n",
i, d1, d2, epsilon, rel_diff(d1, d2));
epsilon /= 10.0;
}
return 0;
}
आउटपुट:
0:3.1415926536 <=> 3.1415929204 within tolerance 1.0000000000 (rel diff 8.4914E-08)
1:3.1415926536 <=> 3.1415929204 within tolerance 0.1000000000 (rel diff 8.4914E-08)
2:3.1415926536 <=> 3.1415929204 within tolerance 0.0100000000 (rel diff 8.4914E-08)
3:3.1415926536 <=> 3.1415929204 within tolerance 0.0010000000 (rel diff 8.4914E-08)
4:3.1415926536 <=> 3.1415929204 within tolerance 0.0001000000 (rel diff 8.4914E-08)
5:3.1415926536 <=> 3.1415929204 within tolerance 0.0000100000 (rel diff 8.4914E-08)
6:3.1415926536 <=> 3.1415929204 within tolerance 0.0000010000 (rel diff 8.4914E-08)
7:3.1415926536 <=> 3.1415929204 within tolerance 0.0000001000 (rel diff 8.4914E-08)
8:3.1415926536 <=> 3.1415929204 out of tolerance 0.0000000100 (rel diff 8.4914E-08)
9:3.1415926536 <=> 3.1415929204 out of tolerance 0.0000000010 (rel diff 8.4914E-08)
सूचक अंकगणित में अतिरिक्त स्केलिंग करना
सूचक अंकगणित में, सूचक को जोड़ने या घटाने के लिए पूर्णांक को पते के परिवर्तन के रूप में नहीं बल्कि स्थानांतरित करने के लिए तत्वों की संख्या के रूप में व्याख्या की जाती है।
#include <stdio.h>
int main(void) {
int array[] = {1, 2, 3, 4, 5};
int *ptr = &array[0];
int *ptr2 = ptr + sizeof(int) * 2; /* wrong */
printf("%d %d\n", *ptr, *ptr2);
return 0;
}
यह कोड ptr2
को ptr2
गए सूचक की गणना में अतिरिक्त स्केलिंग करता है। यदि sizeof(int)
4 है, जो आधुनिक 32-बिट वातावरण में विशिष्ट है, तो अभिव्यक्ति " array[0]
बाद 8 तत्व" array[0]
, जो आउट-ऑफ-रेंज है, और यह अपरिभाषित व्यवहार को आमंत्रित करता है ।
array[0]
बाद 2 तत्व क्या है पर ptr2
बिंदु के लिए, आपको बस 2 जोड़ना चाहिए।
#include <stdio.h>
int main(void) {
int array[] = {1, 2, 3, 4, 5};
int *ptr = &array[0];
int *ptr2 = ptr + 2;
printf("%d %d\n", *ptr, *ptr2); /* "1 3" will be printed */
return 0;
}
Additive ऑपरेटरों का उपयोग करते हुए स्पष्ट सूचक अंकगणित भ्रमित हो सकता है, इसलिए सरणी सबस्क्रिप्टिंग का उपयोग करना बेहतर हो सकता है।
#include <stdio.h>
int main(void) {
int array[] = {1, 2, 3, 4, 5};
int *ptr = &array[0];
int *ptr2 = &ptr[2];
printf("%d %d\n", *ptr, *ptr2); /* "1 3" will be printed */
return 0;
}
E1[E2]
(*((E1)+(E2)))
( N1570 6.5.2.1, पैराग्राफ 2) के समान है, और &(E1[E2])
((E1)+(E2))
बराबर है ( N1570 6.5.3.2, फुटनोट 102)।
वैकल्पिक रूप से, यदि सूचक अंकगणित पसंद किया जाता है, तो एक अलग डेटा प्रकार को संबोधित करने के लिए सूचक को कास्टिंग करना बाइट को संबोधित करने की अनुमति दे सकता है। हालांकि सावधान रहें: धीरज एक मुद्दा बन सकता है, और 'पॉइंटर टू कैरेक्टर' के अलावा अन्य प्रकारों के लिए कास्टिंग करने से अलियासिंग की समस्या पैदा होती है ।
#include <stdio.h>
int main(void) {
int array[3] = {1,2,3}; // 4 bytes * 3 allocated
unsigned char *ptr = (unsigned char *) array; // unsigned chars only take 1 byte
/*
* Now any pointer arithmetic on ptr will match
* bytes in memory. ptr can be treated like it
* was declared as: unsigned char ptr[12];
*/
return 0;
}
मैक्रो सरल स्ट्रिंग प्रतिस्थापन हैं
मैक्रो सरल स्ट्रिंग प्रतिस्थापन हैं। (कड़ाई से बोलते हुए, वे प्रीप्रोसेसिंग टोकन के साथ काम करते हैं, मनमाने तरीके से नहीं।)
#include <stdio.h>
#define SQUARE(x) x*x
int main(void) {
printf("%d\n", SQUARE(1+2));
return 0;
}
आप इस कोड को 9
( 3*3
) प्रिंट करने की उम्मीद कर सकते हैं, लेकिन वास्तव में 5
मुद्रित किया जाएगा क्योंकि मैक्रो का विस्तार 1+2*1+2
।
आपको इस समस्या से बचने के लिए तर्कों और संपूर्ण मैक्रो अभिव्यक्ति को कोष्ठक में लपेटना चाहिए।
#include <stdio.h>
#define SQUARE(x) ((x)*(x))
int main(void) {
printf("%d\n", SQUARE(1+2));
return 0;
}
एक और समस्या यह है कि मैक्रो के तर्कों का एक बार मूल्यांकन करने की गारंटी नहीं है; उनका मूल्यांकन बिल्कुल नहीं किया जा सकता है, या कई बार मूल्यांकन किया जा सकता है।
#include <stdio.h>
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
int main(void) {
int a = 0;
printf("%d\n", MIN(a++, 10));
printf("a = %d\n", a);
return 0;
}
इस कोड में, मैक्रो ((a++) <= (10) ? (a++) : (10))
। चूँकि a++
( 0
) 10
से छोटा है, a++
का मूल्यांकन दो बार किया जाएगा और यह a
का मान बनाएगा और जो MIN
से लौटाया गया है, वह आपसे अलग हो सकता है।
फ़ंक्शंस का उपयोग करके इसे टाला जा सकता है, लेकिन ध्यान दें कि प्रकार फ़ंक्शन परिभाषा द्वारा तय किए जाएंगे, जबकि मैक्रोज़ प्रकारों के लिए लचीला भी हो सकते हैं।
#include <stdio.h>
int min(int x, int y) {
return x <= y ? x : y;
}
int main(void) {
int a = 0;
printf("%d\n", min(a++, 10));
printf("a = %d\n", a);
return 0;
}
अब दोहरे-मूल्यांकन की समस्या तय हो गई है, लेकिन यह min
कार्य, उदाहरण के लिए, बिना काट-छाँट किए double
डेटा से नहीं निपट सकता है।
मैक्रो निर्देश दो प्रकार के हो सकते हैं:
#define OBJECT_LIKE_MACRO followed by a "replacement list" of preprocessor tokens
#define FUNCTION_LIKE_MACRO(with, arguments) followed by a replacement list
मैक्रोज़ के इन दो प्रकारों में क्या अंतर है, वह चरित्र है जो #define
बाद पहचानकर्ता का अनुसरण करता है: यदि यह एक लैपरेन है , तो यह एक फ़ंक्शन जैसा मैक्रो है; अन्यथा, यह ऑब्जेक्ट जैसा मैक्रो है। यदि आशय फ़ंक्शन-जैसे मैक्रो लिखने का है, तो मैक्रो के नाम के अंत के बीच कोई सफेद स्थान नहीं होना चाहिए और (
विस्तृत विवरण के लिए इसे जांचें।
C99 में या बाद में, आप static inline int min(int x, int y) { … }
उपयोग कर सकते हैं।
C11 में, आप min
लिए 'टाइप-जेनेरिक' अभिव्यक्ति लिख सकते हैं।
#include <stdio.h>
#define min(x, y) _Generic((x), \
long double: min_ld, \
unsigned long long: min_ull, \
default: min_i \
)(x, y)
#define gen_min(suffix, type) \
static inline type min_##suffix(type x, type y) { return (x < y) ? x : y; }
gen_min(ld, long double)
gen_min(ull, unsigned long long)
gen_min(i, int)
int main(void)
{
unsigned long long ull1 = 50ULL;
unsigned long long ull2 = 37ULL;
printf("min(%llu, %llu) = %llu\n", ull1, ull2, min(ull1, ull2));
long double ld1 = 3.141592653L;
long double ld2 = 3.141592652L;
printf("min(%.10Lf, %.10Lf) = %.10Lf\n", ld1, ld2, min(ld1, ld2));
int i1 = 3141653;
int i2 = 3141652;
printf("min(%d, %d) = %d\n", i1, i2, min(i1, i2));
return 0;
}
सामान्य अभिव्यक्ति जैसे अधिक प्रकार के साथ बढ़ाया जा सकता double
, float
, long long
, unsigned long
, long
, unsigned
और उचित - gen_min
मैक्रो आमंत्रण लिखा।
लिंक करते समय अनिर्धारित संदर्भ त्रुटियां
संकलन में सबसे आम त्रुटियों में से एक लिंकिंग चरण के दौरान होता है। त्रुटि इस तरह दिखाई देती है:
$ gcc undefined_reference.c
/tmp/ccoXhwF0.o: In function `main':
undefined_reference.c:(.text+0x15): undefined reference to `foo'
collect2: error: ld returned 1 exit status
$
तो आइए उस कोड को देखें जिसने यह त्रुटि उत्पन्न की:
int foo(void);
int main(int argc, char **argv)
{
int foo_val;
foo_val = foo();
return foo_val;
}
हम यहां फू ( int foo();
) की घोषणा करते हैं int foo();
लेकिन इसकी कोई परिभाषा (वास्तविक कार्य) नहीं है। इसलिए हमने फंक्शन हेडर के साथ कंपाइलर प्रदान किया, लेकिन कहीं भी ऐसा फंक्शन परिभाषित नहीं किया गया था, इसलिए कंप्लायंस स्टेज पास हो जाता है लेकिन लिंकर एक Undefined reference
एरर के साथ बाहर निकल जाता है।
हमारे छोटे कार्यक्रम में इस त्रुटि को ठीक करने के लिए हमें केवल foo के लिए एक परिभाषा जोड़ना होगा:
/* Declaration of foo */
int foo(void);
/* Definition of foo */
int foo(void)
{
return 5;
}
int main(int argc, char **argv)
{
int foo_val;
foo_val = foo();
return foo_val;
}
अब यह कोड संकलित करेगा। एक वैकल्पिक स्थिति उत्पन्न होती है जहां के लिए स्रोत foo()
एक अलग स्रोत फ़ाइल में है foo.c
(और वहाँ एक शीर्ष लेख है foo.h
घोषित करने के लिए foo()
है कि दोनों में शामिल है foo.c
और undefined_reference.c
)। फिर फिक्स को दोनों ऑब्जेक्ट फ़ाइल को foo.c
और undefined_reference.c
से लिंक करना है, या दोनों सोर्स फाइल को संकलित करना है:
$ gcc -c undefined_reference.c
$ gcc -c foo.c
$ gcc -o working_program undefined_reference.o foo.o
$
या:
$ gcc -o working_program undefined_reference.c foo.c
$
एक और अधिक जटिल मामला है जहां पुस्तकालय शामिल हैं, जैसे कोड:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char **argv)
{
double first;
double second;
double power;
if (argc != 3)
{
fprintf(stderr, "Usage: %s <denom> <nom>\n", argv[0]);
return EXIT_FAILURE;
}
/* Translate user input to numbers, extra error checking
* should be done here. */
first = strtod(argv[1], NULL);
second = strtod(argv[2], NULL);
/* Use function pow() from libm - this will cause a linkage
* error unless this code is compiled against libm! */
power = pow(first, second);
printf("%f to the power of %f = %f\n", first, second, power);
return EXIT_SUCCESS;
}
कोड वाक्यविन्यास रूप से सही है, pow()
लिए घोषणा pow()
#include <math.h>
से मौजूद है, इसलिए हम संकलन और लिंक करने का प्रयास करते हैं, लेकिन इस तरह एक त्रुटि मिलती है:
$ gcc no_library_in_link.c -o no_library_in_link
/tmp/ccduQQqA.o: In function `main':
no_library_in_link.c:(.text+0x8b): undefined reference to `pow'
collect2: error: ld returned 1 exit status
$
ऐसा इसलिए होता है क्योंकि लिंकिंग स्टेज के दौरान pow()
की परिभाषा नहीं मिली थी। इस हम हम गणित पुस्तकालय कहा जाता है के खिलाफ लिंक करना चाहते हैं निर्दिष्ट करने की आवश्यकता को ठीक करने के libm
निर्दिष्ट द्वारा -lm
झंडा। (ध्यान दें कि ऐसे प्लेटफ़ॉर्म हैं जैसे macOS जहाँ -lm
की ज़रूरत नहीं है, लेकिन जब आपको अपरिभाषित संदर्भ मिलता है, तो लाइब्रेरी की आवश्यकता होती है।)
इसलिए हम फिर से संकलन चरण चलाते हैं, इस बार पुस्तकालय निर्दिष्ट (स्रोत या ऑब्जेक्ट फ़ाइलों के बाद):
$ gcc no_library_in_link.c -lm -o library_in_link_cmd
$ ./library_in_link_cmd 2 4
2.000000 to the power of 4.000000 = 16.000000
$
और यह काम करता है!
गलतफहमी सरणी क्षय
कोड में एक आम समस्या जो बहुआयामी सरणियों, बिंदुओं के सरणियों आदि का उपयोग करती है, यह तथ्य है कि Type**
और Type[M][N]
मौलिक रूप से भिन्न प्रकार हैं:
#include <stdio.h>
void print_strings(char **strings, size_t n)
{
size_t i;
for (i = 0; i < n; i++)
puts(strings[i]);
}
int main(void)
{
char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
print_strings(s, 4);
return 0;
}
नमूना संकलक उत्पादन:
file1.c: In function 'main':
file1.c:13:23: error: passing argument 1 of 'print_strings' from incompatible pointer type [-Wincompatible-pointer-types]
print_strings(strings, 4);
^
file1.c:3:10: note: expected 'char **' but argument is of type 'char (*)[20]'
void print_strings(char **strings, size_t n)
त्रुटि बताती है कि main
फ़ंक्शन में s
सरणी फ़ंक्शन print_strings
को पास की print_strings
, जो एक अलग पॉइंटर प्रकार की अपेक्षा करती है जो इसे प्राप्त हुई। इसमें उस प्रकार को व्यक्त करने वाला एक नोट भी शामिल है जो print_strings
द्वारा print_strings
और वह प्रकार जो इसे main
से पास किया गया था।
यह समस्या सरणी क्षय नामक किसी चीज के कारण है। क्या होता है जब s
अपने प्रकार के साथ char[4][20]
कार्य करने के लिए पारित हो जाता है (20 वर्ण के 4 सरणियों की सरणी) के रूप में यदि आप लिखा था कि वह अपने पहले तत्व के लिए सूचक में बदल जाता है है &s[0]
जो है, प्रकार char (*)[20]
(20 वर्णों के 1 सरणी के लिए सूचक)। यह किसी भी सरणी के लिए होता है, जिसमें बिंदुओं की एक सरणी, सरणियों के सरणी (3-डी सरणियां), और सरणी के लिए बिंदुओं की एक सरणी शामिल है। नीचे एक तालिका दर्शाती है कि एक सरणी के खराब होने पर क्या होता है। प्रकार विवरण में परिवर्तन पर प्रकाश डाला गया है कि क्या होता है:
क्षय से पहले | क्षय के बाद | ||
---|---|---|---|
char [20] | सरणी (20 वर्ण) | char * | सूचक को (1 चार) |
char [4][20] | सरणी ( 20 वर्णों के 4 सरणियों ) | char (*)[20] | सूचक ( 20 वर्णों का 1 सरणी ) |
char *[4] | 4 वर्ण से 1 वर्ण तक का सरणी | char ** | पॉइंटर टू (1 पॉइंटर टू 1 चार) |
char [3][4][20] | सरणी ( 20 वर्णों के 4 सरणियों के 3 सरणियों ) | char (*)[4][20] | सूचक ( 20 वर्णों के 4 सरणियों का 1 सरणी ) |
char (*[4])[20] | 4 वर्णों के 4 अंक ( 20 वर्णों के 1 सरणी के लिए) | char (**)[20] | पॉइंटर टू (1 पॉइंटर से 1 सरणी 20 वर्णों का) |
यदि एक सरणी एक सूचक को क्षय कर सकती है, तो यह कहा जा सकता है कि एक सूचक को कम से कम 1 तत्व का एक सरणी माना जा सकता है। इसका एक अपवाद एक शून्य सूचक है, जो कुछ भी नहीं बताता है और फलस्वरूप एक सरणी नहीं है।
क्षय का क्षय केवल एक बार होता है। यदि किसी सूचक के लिए एक सरणी का क्षय हुआ है, तो वह अब सूचक है, सरणी नहीं। यहां तक कि अगर आपके पास एक सरणी के लिए एक संकेतक है, तो याद रखें कि सूचक को कम से कम एक तत्व का एक सरणी माना जा सकता है, इसलिए सरणी क्षय पहले से ही हुआ है।
दूसरे शब्दों में, एक सरणी ( char (*)[20]
) के लिए एक सूचक कभी भी सूचक ( char **
) का सूचक नहीं बनेगा। print_strings
फ़ंक्शन को ठीक करने के लिए, इसे सही प्रकार प्राप्त करें:
void print_strings(char (*strings)[20], size_t n)
/* OR */
void print_strings(char strings[][20], size_t n)
एक समस्या तब पैदा होती है जब आप चाहते हैं कि print_strings
फ़ंक्शन किसी भी वर्ण के लिए सामान्य हो: क्या होगा यदि 20 के बजाय 30 वर्ण हैं? या ५०? सरणी पैरामीटर से पहले एक और पैरामीटर जोड़ने का उत्तर है:
#include <stdio.h>
/*
* Note the rearranged parameters and the change in the parameter name
* from the previous definitions:
* n (number of strings)
* => scount (string count)
*
* Of course, you could also use one of the following highly recommended forms
* for the `strings` parameter instead:
*
* char strings[scount][ccount]
* char strings[][ccount]
*/
void print_strings(size_t scount, size_t ccount, char (*strings)[ccount])
{
size_t i;
for (i = 0; i < scount; i++)
puts(strings[i]);
}
int main(void)
{
char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
print_strings(4, 20, s);
return 0;
}
इसे संकलित करने से अपेक्षित परिणाम में कोई त्रुटि और परिणाम नहीं होता है:
Example 1
Example 2
Example 3
Example 4
"वास्तविक" बहुआयामी सरणियों की अपेक्षा कार्यों के लिए असंगत सरणियों को पास करना
जब साथ बहुआयामी सरणियों आवंटन malloc
, calloc
, और realloc
, एक आम पैटर्न एकाधिक कॉल (भले ही कॉल केवल एक बार दिखाई देता है, एक पाश में हो सकता है) के साथ आंतरिक सरणियों आवंटित करने के लिए है:
/* Could also be `int **` with malloc used to allocate outer array. */
int *array[4];
int i;
/* Allocate 4 arrays of 16 ints. */
for (i = 0; i < 4; i++)
array[i] = malloc(16 * sizeof(*array[i]));
एक आंतरिक सरणियों के अंतिम तत्व और अगले आंतरिक सरणी के पहले तत्व के बीच बाइट्स में अंतर 0 नहीं हो सकता है क्योंकि वे एक "वास्तविक" बहुआयामी सरणी (उदाहरण के लिए int array[4][16];
) के साथ होगा। :
/* 0x40003c, 0x402000 */
printf("%p, %p\n", (void *)(array[0] + 15), (void *)array[1]);
int
के आकार को ध्यान में रखते हुए, आपको 8128 बाइट्स (8132-4) का अंतर मिलता है, जो कि 2032 int
-sized सरणी तत्व हैं, और यही समस्या है: "वास्तविक" बहुआयामी सरणी में तत्वों के बीच कोई अंतराल नहीं है।
यदि आपको "वास्तविक" बहुआयामी सरणी की अपेक्षा करने वाले फ़ंक्शन के साथ गतिशील रूप से आवंटित सरणी का उपयोग करने की आवश्यकता है, तो आपको टाइप int *
का ऑब्जेक्ट आवंटित करना चाहिए और गणना करने के लिए अंकगणितीय का उपयोग करना चाहिए:
void func(int M, int N, int *array);
...
/* Equivalent to declaring `int array[M][N] = {{0}};` and assigning to array4_16[i][j]. */
int *array;
int M = 4, N = 16;
array = calloc(M, N * sizeof(*array));
array[i * N + j] = 1;
func(M, N, array);
यदि N
एक वैरिएबल के बजाय एक मैक्रो या पूर्णांक शाब्दिक है, तो कोड एक सरणी को पॉइंटर आवंटित करने के बाद अधिक प्राकृतिक 2-डी सरणी संकेतन का उपयोग कर सकता है:
void func(int M, int N, int *array);
#define N 16
void func_N(int M, int (*array)[N]);
...
int M = 4;
int (*array)[N];
array = calloc(M, sizeof(*array));
array[i][j] = 1;
/* Cast to `int *` works here because `array` is a single block of M*N ints with no gaps,
just like `int array2[M * N];` and `int array3[M][N];` would be. */
func(M, N, (int *)array);
func_N(M, array);
यदि N
मैक्रो या पूर्णांक शाब्दिक नहीं है, तो array
एक चर-लंबाई सरणी (वीएलए) को इंगित करेगी। यह अभी भी साथ इस्तेमाल किया जा सकता func
कास्ट कर int *
और एक नया कार्य func_vla
जगह लेंगे func_N
:
void func(int M, int N, int *array);
void func_vla(int M, int N, int array[M][N]);
...
int M = 4, N = 16;
int (*array)[N];
array = calloc(M, sizeof(*array));
array[i][j] = 1;
func(M, N, (int *)array);
func_vla(M, N, array);
नोट : वीएलए सी 11 के रूप में वैकल्पिक हैं। यदि आपका कार्यान्वयन C11 का समर्थन करता है और मैक्रो __STDC_NO_VLA__
को 1 में परिभाषित करता है, तो आप पूर्व C99 विधियों के साथ अटके हुए हैं।
स्ट्रिंग शाब्दिक के बजाय चरित्र स्थिरांक का उपयोग करना, और इसके विपरीत
सी में, चरित्र स्थिरांक और स्ट्रिंग शाब्दिक अलग-अलग चीजें हैं।
'A 'a'
जैसे सिंगल कोट्स से घिरा एक किरदार स्थिर होता है । एक चरित्र स्थिरांक एक पूर्णांक होता है जिसका मान वर्ण कोड होता है जो वर्ण के लिए होता है। कई पात्रों के साथ चरित्र स्थिरांक की व्याख्या कैसे करें जैसे 'abc'
कार्यान्वयन-परिभाषित है।
"abc"
जैसे दोहरे उद्धरणों से घिरे शून्य या अधिक वर्ण एक स्ट्रिंग शाब्दिक है । एक स्ट्रिंग शाब्दिक एक अपरिवर्तनीय सरणी है जिसके तत्व प्रकार char
। दोहरे उद्धरणों में स्ट्रिंग शून्य-वर्ण को समाप्त करने वाली सामग्री है, इसलिए "abc"
में 4 तत्व ( {'a', 'b', 'c', '\0'}
) हैं।
इस उदाहरण में, एक वर्ण स्थिरांक का उपयोग किया जाता है जहां एक स्ट्रिंग शाब्दिक का उपयोग किया जाना चाहिए। यह चरित्र निरंतर कार्यान्वयन-परिभाषित तरीके से एक पॉइंटर में परिवर्तित हो जाएगा और परिवर्तित पॉइंटर को वैध होने की बहुत कम संभावना है, इसलिए यह उदाहरण अपरिभाषित व्यवहार को आमंत्रित करेगा।
#include <stdio.h>
int main(void) {
const char *hello = 'hello, world'; /* bad */
puts(hello);
return 0;
}
इस उदाहरण में, एक स्ट्रिंग शाब्दिक का उपयोग किया जाता है जहां एक चरित्र स्थिरांक का उपयोग किया जाना चाहिए। स्ट्रिंग शाब्दिक से परिवर्तित सूचक कार्यान्वयन-परिभाषित तरीके से पूर्णांक में परिवर्तित हो जाएगा, और इसे कार्यान्वयन-परिभाषित तरीके से char
में परिवर्तित किया जाएगा। (पूर्णांक को किसी हस्ताक्षरित प्रकार में कैसे परिवर्तित किया जाए जो परिवर्तित करने के लिए मूल्य का प्रतिनिधित्व नहीं कर सकता है, कार्यान्वयन-परिभाषित है, और क्या char
पर हस्ताक्षर किए गए भी कार्यान्वयन-परिभाषित है।) आउटपुट कुछ अर्थहीन होगा।
#include <stdio.h>
int main(void) {
char c = "a"; /* bad */
printf("%c\n", c);
return 0;
}
लगभग सभी मामलों में, कंपाइलर इन मिक्स-अप के बारे में शिकायत करेगा। यदि ऐसा नहीं होता है, तो आपको अधिक संकलक चेतावनी विकल्पों का उपयोग करने की आवश्यकता है, या यह अनुशंसा की जाती है कि आप एक बेहतर संकलक का उपयोग करें।
पुस्तकालय के कार्यों के रिटर्न मान को अनदेखा करना
C मानक पुस्तकालय में लगभग प्रत्येक फ़ंक्शन सफलता पर कुछ देता है, और त्रुटि पर कुछ और। उदाहरण के लिए, malloc
सफलता पर फ़ंक्शन द्वारा आवंटित मेमोरी ब्लॉक में एक पॉइंटर लौटाएगा, और, यदि फ़ंक्शन मेमोरी के अनुरोधित ब्लॉक, एक शून्य पॉइंटर को आवंटित करने में विफल रहा। इसलिए आपको हमेशा आसान डीबगिंग के लिए रिटर्न वैल्यू की जांच करनी चाहिए।
ये गलत है:
char* x = malloc(100000000000UL * sizeof *x);
/* more code */
scanf("%s", x); /* This might invoke undefined behaviour and if lucky causes a segmentation violation, unless your system has a lot of memory */
यह अच्छा है:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char* x = malloc(100000000000UL * sizeof *x);
if (x == NULL) {
perror("malloc() failed");
exit(EXIT_FAILURE);
}
if (scanf("%s", x) != 1) {
fprintf(stderr, "could not read string\n");
free(x);
exit(EXIT_FAILURE);
}
/* Do stuff with x. */
/* Clean up. */
free(x);
return EXIT_SUCCESS;
}
इस तरह आप त्रुटि का कारण तुरंत जान जाते हैं, अन्यथा आप पूरी तरह से गलत जगह पर बग की तलाश में घंटों बिता सकते हैं।
न्यूलाइन वर्ण का उपयोग ठेठ स्कैनफ () कॉल में नहीं किया जाता है
जब यह कार्यक्रम
#include <stdio.h>
#include <string.h>
int main(void) {
int num = 0;
char str[128], *lf;
scanf("%d", &num);
fgets(str, sizeof(str), stdin);
if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
printf("%d \"%s\"\n", num, str);
return 0;
}
इस इनपुट के साथ निष्पादित किया जाता है
42
life
आउटपुट 42 ""
की अपेक्षा 42 "life"
।
ऐसा इसलिए है क्योंकि 42
बाद की एक नई लाइन वर्ण को scanf()
के कॉल में उपभोग नहीं किया जाता है और life
पढ़ने से पहले इसे fgets()
द्वारा भस्म किया जाता है। फिर, life
पढ़ने से पहले fgets()
पढ़ना बंद कर दें।
इस समस्या से बचने के लिए, एक तरीका जो एक पंक्ति की अधिकतम लंबाई ज्ञात होने पर उपयोगी होता है - ऑनलाइन जज सिस्ट में समस्याओं को हल करते समय, उदाहरण के लिए - सीधे scanf()
का उपयोग करने से और fgets()
माध्यम से सभी लाइनों को पढ़ने से बचना है। आप पढ़ी गई लाइनों को पार्स करने के लिए sscanf()
का उपयोग कर सकते हैं।
#include <stdio.h>
#include <string.h>
int main(void) {
int num = 0;
char line_buffer[128] = "", str[128], *lf;
fgets(line_buffer, sizeof(line_buffer), stdin);
sscanf(line_buffer, "%d", &num);
fgets(str, sizeof(str), stdin);
if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
printf("%d \"%s\"\n", num, str);
return 0;
}
एक अन्य तरीका यह है कि जब तक आप scanf()
का उपयोग करने के बाद और fgets()
का fgets()
से पहले एक नया वर्ण नहीं मारते तब तक पढ़ें।
#include <stdio.h>
#include <string.h>
int main(void) {
int num = 0;
char str[128], *lf;
int c;
scanf("%d", &num);
while ((c = getchar()) != '\n' && c != EOF);
fgets(str, sizeof(str), stdin);
if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
printf("%d \"%s\"\n", num, str);
return 0;
}
एक अर्धचालक को एक #define में जोड़ना
सी प्रीप्रोसेसर में भ्रमित होना आसान है, और इसे सी के हिस्से के रूप में मानते हैं, लेकिन यह एक गलती है क्योंकि प्रीप्रोसेसर सिर्फ एक पाठ प्रतिस्थापन तंत्र है। उदाहरण के लिए, यदि आप लिखते हैं
/* WRONG */
#define MAX 100;
int arr[MAX];
कोड का विस्तार होता है
int arr[100;];
जो एक सिंटैक्स त्रुटि है। उपाय है #define
लाइन से अर्धविराम को हटाना। एक अर्धविराम के साथ #define
को समाप्त करना लगभग हमेशा एक गलती है।
मल्टी-लाइन टिप्पणियों को नेस्टेड नहीं किया जा सकता है
सी में, बहु-पंक्ति टिप्पणियां, / * और * /, घोंसला न करें।
यदि आप टिप्पणी की इस शैली का उपयोग करके कोड या फ़ंक्शन के किसी खंड को एनोटेट करते हैं:
/*
* max(): Finds the largest integer in an array and returns it.
* If the array length is less than 1, the result is undefined.
* arr: The array of integers to search.
* num: The number of integers in arr.
*/
int max(int arr[], int num)
{
int max = arr[0];
for (int i = 0; i < num; i++)
if (arr[i] > max)
max = arr[i];
return max;
}
आप इसे आसानी से टिप्पणी नहीं कर पाएंगे:
//Trying to comment out the block...
/*
/*
* max(): Finds the largest integer in an array and returns it.
* If the array length is less than 1, the result is undefined.
* arr: The array of integers to search.
* num: The number of integers in arr.
*/
int max(int arr[], int num)
{
int max = arr[0];
for (int i = 0; i < num; i++)
if (arr[i] > max)
max = arr[i];
return max;
}
//Causes an error on the line below...
*/
एक समाधान C99 शैली टिप्पणियों का उपयोग करना है:
// max(): Finds the largest integer in an array and returns it.
// If the array length is less than 1, the result is undefined.
// arr: The array of integers to search.
// num: The number of integers in arr.
int max(int arr[], int num)
{
int max = arr[0];
for (int i = 0; i < num; i++)
if (arr[i] > max)
max = arr[i];
return max;
}
अब पूरे ब्लॉक को आसानी से कमेंट किया जा सकता है:
/*
// max(): Finds the largest integer in an array and returns it.
// If the array length is less than 1, the result is undefined.
// arr: The array of integers to search.
// num: The number of integers in arr.
int max(int arr[], int num)
{
int max = arr[0];
for (int i = 0; i < num; i++)
if (arr[i] > max)
max = arr[i];
return max;
}
*/
एक अन्य समाधान, टिप्पणी वाक्य-विन्यास का उपयोग कर का उपयोग कर अक्षम करने कोड से बचने के लिए है #ifdef
या #ifndef
बजाय पूर्वप्रक्रमक निर्देशों। इन निर्देशों के घोंसला कर आप शैली आप पसंद करते हैं में अपने कोड टिप्पणी करने के लिए मुक्त हो जाता है।
#define DISABLE_MAX /* Remove or comment this line to enable max() code block */
#ifdef DISABLE_MAX
/*
* max(): Finds the largest integer in an array and returns it.
* If the array length is less than 1, the result is undefined.
* arr: The array of integers to search.
* num: The number of integers in arr.
*/
int max(int arr[], int num)
{
int max = arr[0];
for (int i = 0; i < num; i++)
if (arr[i] > max)
max = arr[i];
return max;
}
#endif
कुछ मार्गदर्शिकाएँ इतनी दूर तक जाती हैं कि यह अनुशंसा करने के लिए कि कोड अनुभागों पर कभी टिप्पणी नहीं की जानी चाहिए और यदि कोड को अस्थायी रूप से अक्षम किया जाना है तो कोई #if 0
निर्देश का उपयोग कर सकता है।
कोड अनुभागों को ब्लॉक करने के लिए #if 0 देखें।
सरणी सीमाओं को ओवरस्टेपिंग
Arrays शून्य-आधारित हैं, यह सूचकांक हमेशा 0 से शुरू होता है और इंडेक्स ऐरे लेंथ माइनस 1 के साथ समाप्त होता है। इस प्रकार निम्नलिखित कोड एरे के पहले तत्व को आउटपुट नहीं करेगा और अंतिम मूल्य के लिए कचरा का उत्पादन करेगा जो इसे प्रिंट करता है।
#include <stdio.h>
int main(void)
{
int x = 0;
int myArray[5] = {1, 2, 3, 4, 5}; //Declaring 5 elements
for(x = 1; x <= 5; x++) //Looping from 1 till 5.
printf("%d\t", myArray[x]);
printf("\n");
return 0;
}
आउटपुट: 2 3 4 5 GarbageValue
निम्नलिखित वांछित उत्पादन प्राप्त करने का सही तरीका दर्शाता है:
#include <stdio.h>
int main(void)
{
int x = 0;
int myArray[5] = {1, 2, 3, 4, 5}; //Declaring 5 elements
for(x = 0; x < 5; x++) //Looping from 0 till 4.
printf("%d\t", myArray[x]);
printf("\n");
return 0;
}
आउटपुट: 1 2 3 4 5
इसके साथ काम करने से पहले एक सरणी की लंबाई जानना महत्वपूर्ण है क्योंकि आप बफर को भ्रष्ट कर सकते हैं या सीमा से बाहर स्मृति स्थानों तक पहुंच कर एक विभाजन दोष का कारण बन सकते हैं।
पुनरावर्ती कार्य - आधार स्थिति को याद कर रहा है
किसी संख्या के भाज्य की गणना एक पुनरावर्ती कार्य का एक उत्कृष्ट उदाहरण है।
आधार स्थिति गुम है:
#include <stdio.h>
int factorial(int n)
{
return n * factorial(n - 1);
}
int main()
{
printf("Factorial %d = %d\n", 3, factorial(3));
return 0;
}
विशिष्ट आउटपुट: Segmentation fault: 11
इस फ़ंक्शन के साथ समस्या यह है कि यह तेजी से लूप होगा, जिससे एक विभाजन दोष हो सकता है - इसे पुनरावृत्ति को रोकने के लिए एक आधार स्थिति की आवश्यकता होती है।
आधार स्थिति घोषित:
#include <stdio.h>
int factorial(int n)
{
if (n == 1) // Base Condition, very crucial in designing the recursive functions.
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
int main()
{
printf("Factorial %d = %d\n", 3, factorial(3));
return 0;
}
नमूना उत्पादन
Factorial 3 = 6
जैसे ही n
स्थिति 1 के बराबर होती है यह फ़ंक्शन समाप्त हो जाएगा (बशर्ते n
का प्रारंभिक मूल्य काफी छोटा है - ऊपरी सीमा 12
जब int
32-बिट मात्रा है)।
नियमों का पालन किया जाना चाहिए:
- एल्गोरिथ्म को प्रारंभ करें। पुनरावर्ती कार्यक्रमों को अक्सर शुरू करने के लिए एक बीज मूल्य की आवश्यकता होती है। यह या तो फ़ंक्शन के लिए दिए गए पैरामीटर का उपयोग करके या गैर-पुनरावर्ती है जो गेटवे फ़ंक्शन प्रदान करके या फिर पुनरावर्ती गणना के लिए बीज मान सेट करता है।
- यह देखने के लिए जांचें कि क्या संसाधित किया जा रहा है वर्तमान मान बेस केस से मेल खाता है या नहीं। यदि हां, तो प्रक्रिया करें और मान लौटाएं।
- एक छोटी या सरल उप-समस्या या उप-समस्याओं के संदर्भ में उत्तर को फिर से परिभाषित करें।
- उप-समस्या पर एल्गोरिथ्म चलाएँ।
- उत्तर के निर्माण में परिणामों को मिलाएं।
- परिणाम लौटाएं।
स्रोत: पुनरावर्ती कार्य
'सत्य' के विरुद्ध तार्किक अभिव्यक्ति की जाँच
मूल सी मानक का कोई आंतरिक बूलियन प्रकार नहीं था, इसलिए bool
, true
और false
का कोई अंतर्निहित अर्थ नहीं था और अक्सर प्रोग्रामर द्वारा परिभाषित किया गया था। आमतौर पर true
को 1 के रूप में परिभाषित किया जाएगा और false
को 0 के रूप में परिभाषित किया जाएगा।
C99 बिल्ट-इन प्रकार _Bool
और हेडर <stdbool.h>
जो bool
( _Bool
विस्तार), false
और true
को परिभाषित करता true
। यह आपको bool
, true
और false
को फिर से परिभाषित करने की अनुमति देता है, लेकिन ध्यान दें कि यह एक अश्लील विशेषता है।
इससे भी महत्वपूर्ण बात यह है कि तार्किक अभिव्यक्तियां शून्य के रूप में मूल्यांकन करने वाली किसी भी चीज को असत्य और किसी गैर-शून्य मूल्यांकन को सत्य मानती हैं। उदाहरण के लिए:
/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
if ((bitField & 0x80) == true) /* Comparison only succeeds if true is 0x80 and bitField has that bit set */
{
return true;
}
else
{
return false;
}
}
उपरोक्त उदाहरण में, समारोह अगर ऊपरी बिट सेट किया गया है की जाँच करें और वापस जाने के लिए कोशिश कर रहा है true
अगर यह होता है। हालांकि, स्पष्ट रूप से के खिलाफ जाँच करके true
, if
बयान को केवल तभी सफल होगा (bitfield & 0x80)
मूल्यांकन करता है जो कुछ भी करने के लिए true
परिभाषित किया गया है, के रूप में जो आम तौर पर है 1
और बहुत ही कम 0x80
। या तो स्पष्ट रूप से आपके द्वारा अपेक्षित मामले के खिलाफ जाँच करें:
/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
if ((bitField & 0x80) == 0x80) /* Explicitly test for the case we expect */
{
return true;
}
else
{
return false;
}
}
या किसी भी गैर-शून्य मान का सही मूल्यांकन करें।
/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
/* If upper bit is set, result is 0x80 which the if will evaluate as true */
if (bitField & 0x80)
{
return true;
}
else
{
return false;
}
}
फ़्लोटिंग पॉइंट शाब्दिक डिफ़ॉल्ट रूप से डबल प्रकार के होते हैं
ध्यान रखना चाहिए जब प्रकार के चर को शाब्दिक मानों पर float
हुए या शाब्दिक मूल्यों के साथ तुलना करते हैं, क्योंकि नियमित फ्लोटिंग पॉइंट शाब्दिक जैसे 0.1
double
। इससे आश्चर्य हो सकता है:
#include <stdio.h>
int main() {
float n;
n = 0.1;
if (n > 0.1) printf("Wierd\n");
return 0;
}
// Prints "Wierd" when n is float
यहाँ, n
को इनिशियलाइज़ किया गया है और एकल परिशुद्धता के लिए गोल किया गया है, जिसके परिणामस्वरूप मूल्य 0.10000000149011612 है। फिर, n
को 0.1
शाब्दिक (जो कि 0.10000000000000001 के बराबर है) के साथ तुलना करने के लिए डबल परिशुद्धता में परिवर्तित किया जाता है, जिसके परिणामस्वरूप एक बेमेल होता है।
राउंडिंग एरर के अलावा, double
लिटरल के साथ float
वैरिएबल मिक्स करने से प्लेटफॉर्म पर खराब प्रदर्शन होगा, जिसमें double
प्रिसिजन के लिए हार्डवेयर सपोर्ट नहीं है।