C陷阱与缺陷学习笔记
生活随笔
收集整理的這篇文章主要介紹了
C陷阱与缺陷学习笔记
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
導(dǎo)讀
程序是由符號(token)序列所組成的,將程序分解成符號的過程,成為“詞法分析”。
符號構(gòu)成更大的單元--語句和聲明,語法細(xì)節(jié)最終決定了語義。
詞法陷阱
符號(token)指的是程序的一個基本組成單元,其作用相當(dāng)于一個句子中的單詞。編譯器中負(fù)責(zé)將程序分解為一個一個符號的部分,稱作“詞法分析器”。 在C語言中,符號之間的空白(/b /t /n...)將被忽略。 #include <stdio.h> int main() {if(1)printf("Hello World\n");return0; }C語言中char類型都是當(dāng)做int類型來處理的
如果一個整型常量的第一個字符是數(shù)字0,那么該常量將被視為八進制數(shù)。 因此10與010的含義截然不同。 用雙引號引起來的字符串,代表的是一個指向無名數(shù)組起始字符的指針, 該數(shù)組被雙引號之間的字符以及一個額外的二進制為零的字符'\0'初始化。 #include <stdio.h>int main() {printf("%c\n", "hello"[0]);printf("%c\n", "hello"[1]);printf("%c\n", "hello"[2]);printf("%c\n", "hello"[3]);printf("%c\n", "hello"[4]); }輸出 h e l l o
printf("%s\n", "hello");
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
printf("%s\n", str);
二者是等價的。
語法陷阱
函數(shù)調(diào)用: void fun() { printf("Hello World"); } 在C語言中,函數(shù)調(diào)用即使不帶參數(shù),也應(yīng)該包括參數(shù)列表。 調(diào)用方式:fun();(這僅僅是一種簡寫的形式) 而實際上真正的調(diào)用語法為:(*fun)(); 理解函數(shù)聲明:(使用添加括號的方式)語義陷阱
單純意義上的數(shù)組只有兩種操作: 1.確定數(shù)組的大小sizeof(arr) / sizeof(arr[0]) 2.獲得指向該數(shù)組下標(biāo)為0的元素的指針。 其他操作都是通過指針進行的。 關(guān)于數(shù)組名取地址: 在C中, 在幾乎所有使用數(shù)組的表達(dá)式中,數(shù)組名的值是個指針常量,也就是數(shù)組第一個元素的地址。 它的類型取決于數(shù)組元素的類型: 如果它們是int類型,那么數(shù)組名的類型就是“指向int的常量指針“。在以下兩種場合下,數(shù)組名并不是用指針常量來表示,就是當(dāng)數(shù)組名作為sizeof操作符和單目操作符&的操作數(shù)時。 sizeof返回整個數(shù)組的長度,而不是指向數(shù)組的指針的長度。 取一個數(shù)組名的地址所產(chǎn)生的是一個指向數(shù)組的指針,而不是一個指向某個指針常量的指針。所以&a后返回的指針便是指向數(shù)組的指針,跟a在指針的類型上是有區(qū)別的。 #include <stdio.h> int main() {int arr[] = {1, 2, 3, 4, 5};int *p = arr;printf("%d\n", *p);p++;printf("%d\n", *p);int (*ptr)[5];ptr = &arr;//&arr的類型為 int(*)[5]printf("%d\n", **ptr);p = *ptr;p++;printf("%d\n", *p);return 0; }ptr/&arr類型為:int(*)[5]; *ptr/p類型為:int*; arr類型為:int* const p;//指針常量 #include <stdio.h>int main() {int a[5] = {1, 2, 3, 4, 5};int *ptr = (int*)(&a + 1);int array[2][3] = {{1, 2, 3}, {4, 5, 6}};printf("%d %d %d\n", a, &a, ptr);printf("%d %d\n", *(a + 1), *(ptr - 1));printf("%d %d %d %d\n", array, array[0], &array[0][0], &array);printf("%d %d %d %d\n", array + 1, array[0] + 1, &array[0][0] + 1, &array + 1); }從以上輸出我們可以看出:數(shù)組名和數(shù)組名取地址在數(shù)值上是相同的,均表示數(shù)組第一個元素的地址。但是二者的顆粒度不同。
當(dāng)數(shù)組是一維數(shù)組時,數(shù)組名是以一個數(shù)組元素為顆粒度,表現(xiàn)為“當(dāng)數(shù)組名加1時,這里的1表示一個數(shù)組元素單元”,例子中的數(shù)組元素為整數(shù),所以數(shù)組名加1時地址加4;而數(shù)組名取地址&以整個數(shù)組為顆粒度,表現(xiàn)為“當(dāng)數(shù)組名取地址&加1時,這里的1是表示整個數(shù)組單元”,例子中的數(shù)組為有5個元素的整型數(shù)組,所以數(shù)組名取地址&加1時,地址加20.
當(dāng)數(shù)組是二維數(shù)組時,數(shù)組名array、array[0]、&array[0][0]以及數(shù)組名取地址&在數(shù)值上是相同的,同樣各個之間的顆粒度不同。其中array[0]以及 &array[0][0] 的顆粒度相同,均是以一個數(shù)組元素為顆粒度,所以它們加1后,地址加4;而數(shù)組名和數(shù)組名取地址&顆粒度不同,前者以一行元素為顆粒度,后者以整個數(shù)組單元為顆粒度,所以前者加1,地址加3*4,后者加1,地址加6*4.
二維數(shù)組:以數(shù)組作為元素的數(shù)組。 計算機中只有一維數(shù)組,二維數(shù)組只是虛擬出來的概念。 int arr[4][5]; arr的類型為:int(*)[5] int (*ptr)[5]; ptr = arr;//arr->指向數(shù)組的指針。
#define NULL ? ?((void *)0)
#include <stdio.h> int main() {char *p = NULL;if (p == 0){puts("YES");}return 0; }輸出:YES 原書: char *p = "Hello"; p[0] = 'h'; 這樣的操作是是錯誤的,因為p的類型為const char *,即指向常量的指針。 ANSI標(biāo)準(zhǔn)禁止對string literal作出修改。 數(shù)組作為參數(shù),數(shù)組名立刻被轉(zhuǎn)換成指向該數(shù)組第一個元素的指針,因為C語言中我們沒有辦法將一個數(shù)組 作為函數(shù)參數(shù)直接傳遞。 所以: char *str = "12345"; printf("%s\n", str);完全等價于:printf("%s\n", &str[0]);
在C語言中,string literal代表了一塊包括字符串中所有字符以及一個空字符('\0')的內(nèi)存區(qū)域的地址。 C99允許變長數(shù)組,所以一下操作也是正確的(C89則錯誤): #include <stdio.h> #include <string.h> int main() {char *p1 = "12345";char *p2 = "67";char arr[strlen(p1) + strlen(p2)];//memset(arr, 0, sizeof(arr));strcpy(arr, p1);//strcat(arr, p1);strcat(arr, p2);puts(arr);return 0; }
c語言中存在兩類整數(shù)算術(shù)運算,有符號運算和無符號運算。在無符號運算里,沒有了符號位,所以是沒有溢出的概念的。
所有的無符號運算都是以2的n次方為模。如果算術(shù)運算符的一個操作數(shù)是有符號書,另一個是無符號數(shù),那么有符號數(shù)
會被轉(zhuǎn)換為無符號數(shù)(表示范圍小的總是被轉(zhuǎn)換為表示范圍大的),那么溢出也不會發(fā)生。但是,當(dāng)兩個操作數(shù)都是有符號數(shù)
時,溢出就有可能發(fā)生。而且溢出的結(jié)果是未定義的。當(dāng)一個運算的結(jié)果發(fā)生溢出時,任何假設(shè)都是不安全的。
例如,假定a和b是兩個非負(fù)的整型變量(有符號),我們需要檢查a+b是否溢出,一種想當(dāng)然的方式是:
if (a + b < 0)
? ? ? 溢出;
實際上,在現(xiàn)實世界里,這并不能正常運行。當(dāng)a+b確實發(fā)生溢出時,所有關(guān)于結(jié)果如何的假設(shè)均不可靠。比如,在某些
機器的cpu,加法運算將設(shè)置一個內(nèi)部寄存器為四種狀態(tài):正,負(fù),零和溢出。在這種機器上,c編譯器完全有理由實現(xiàn)以上
的例子,使得a+b返回的不是負(fù),而是這個內(nèi)存寄存器的溢出狀態(tài)。顯然,if的判斷會失敗。
一種正確的方式是將a和b都強制轉(zhuǎn)換為無符號整數(shù):
if ( (unsigned)a + (unsigned)b ?> INT_MAX)
? ? ? 溢出;
這里的int_max值為有符號整型的最大值。在一般的編譯器里是一個預(yù)定義的常量。ANSI C在limits里定義了INT_MAX,值為
2的31次方-1.?
不需要用到無符號算數(shù)運算的另一種可行方法是:
if (a > INT_MAX - b )
? ? ?溢出;
PS : 有符號數(shù)的最高位(31位)為符號位,最高位為0的時候,表示正,為1的時候表示負(fù)。運算時,符號位不參加運算,但是如果兩個數(shù)相加,30位需要進1時,那么即表示溢出。
| 如何檢測整型相加溢出(overflow) 前言: 本文主要討論如何判斷整型相加溢出(overflow)的問題. 我們知道計算機里面整型一般是有限個字節(jié)(4 bytes for int)表示, 正是因為只能用有限個字節(jié)表示一個整型變量, 由此帶來一個可能的問題: 溢出(overflow). 所謂整型溢出(overflow), 是說一個整數(shù)的值太大或者太小導(dǎo)致沒有用給定的有限個(比如四個字節(jié)沒法存超過2^31 – 1的有符號正整數(shù))字節(jié)存儲表示. 這個整型溢出(overflow)問題一般的時候不會注意到也并不危險, 但是在做整型加法或者乘法的時候就有可能出現(xiàn)并且給程序帶來未定義的行為. 這里我們主要討論如何判斷整型相加溢出(overflow)的兩種方法以及各自優(yōu)缺點. 整型相加溢出(overflow)的原因: 前言里面也已經(jīng)提到了, 計算機中的的整數(shù)是用有限個字節(jié)表示的, 假設(shè)用k個字節(jié)表示一個整型變量, 那么這個變量可以表示的有符號整數(shù)的范圍是-2^(8k-1) ~ 2^(8k-1) – 1 ?, ?那么兩個正整數(shù)或者兩個負(fù)整數(shù)相加就有可能超過這個整型變量所能表示的范圍, 向上超出>2^(8k-1) – 1我們稱之為向上溢出, 向下超出<-2^(8k-1), 我們稱之為向下溢出. 注意這里兩個整數(shù)符號相同是整型相加溢出(overflow)的必要條件, 也就是說只有符號相同的兩個整數(shù)相加才有可能產(chǎn)生溢出(overflow)問題. 這個可以這么理解: 你想要是兩個符號不同的兩個整數(shù), 他們相加, 那么這個和的值的絕對值一定是比單個相加數(shù)和被相加數(shù)都小, 既然相加數(shù)和被相加數(shù)都能用現(xiàn)有整型變量表示, 那么兩個不同整數(shù)的相加結(jié)果怎么樣都可以用現(xiàn)有的整型范圍的變量存儲下來而不溢出(overflow). 所以結(jié)論: 只有符號相同的整數(shù)相加才有可能才生溢出(overflow). 整型相加溢出(overflow)的檢測: 那么接下來的問題就是如何檢測到溢出(overflow)的產(chǎn)生的, 更具體的, 給定兩個整型, 比如int a, int b, 我們做加法a+b, 如何去判斷這個相加的結(jié)果是正確結(jié)果還是說是溢出(overflow)的結(jié)果. 下面我們給出兩種方法(后面我們會討論方法二是更好的方法), 并且做出解釋或者說不太嚴(yán)格的證明方法的正確性, 也就是為什么這么做就能保證溢出(overflow)的檢測的正確性. 方法一, 計算相加的結(jié)果, 判斷結(jié)果的符號, 兩個正整數(shù)相加結(jié)果為負(fù)數(shù), 或者兩個負(fù)整數(shù)相加結(jié)果為正數(shù), 那么就是溢出(overflow)了. 實現(xiàn)代碼如下: int addInt(int a, int b) { ? ? int res = a + b; ? ? if(a > 0 && b > 0 && res < 0) throw overflow_exception; ? ? if(a < 0 && b < 0 && res > 0) throw overflow_exception; ? ? return res; } 這個方法的原理是這樣, 計算機里面有符號整數(shù)是利用補碼的形式表示的, 第一位是符號位, 0表示整數(shù), 1表示負(fù)數(shù). 我們拿一個字節(jié)的整型來舉例, 一個字節(jié)的有符號數(shù)可以表示的范圍就是-128 ~ 127, 那么兩個一個字節(jié)的正整數(shù)相加的最大范圍就是254, 那么其中128 ~ 254就是溢出(overflow)的值, 是不能用一個字節(jié)存儲下的值, 這個值用一個字節(jié)表示的時候最高位是1, 在有符號整數(shù)系統(tǒng)里面這個值其實被當(dāng)成了負(fù)數(shù). 同理, 負(fù)數(shù)相加的時候最小可以到達(dá)-256, 根據(jù)補碼的表示對應(yīng)的正整數(shù)取反加1就是對應(yīng)的補碼, 那么對應(yīng)的正整數(shù)的最高位是1, 現(xiàn)在取反以后就變成0, 也就是說兩個比較大的負(fù)數(shù)相加的結(jié)果其實變成了正數(shù). 這就是上述方法的理論基礎(chǔ). 方法二, 使用減法, 利用現(xiàn)有整型的最大或者最小極值減去某個加數(shù)(減法相當(dāng)于變號, 從而保證沒有溢出(overflow)的發(fā)生), 和另一個加數(shù)比較大小進行判斷. 實現(xiàn)代碼如下: int addInt(int a, int b) { ? ? if(a > 0 && b > 0 && a > INT_MAX - b) throw overflow_exception; ? ? if(a < 0 && b < 0 && a < INT_MIN - b) throw overflow_exception; ? ? return a + b; } 這個方法其實不用太多的解釋, 簡單的數(shù)學(xué)知識就能解釋其中的原理, 由于減法保證了不會溢出(overflow), 又前面我們保證了兩個數(shù)都是正整數(shù), 所以形如 a > INT_MAX – b的判斷是安全并且總是正確的. 而且這個檢測方法的正確性可以通過移位就看得懂了, 不像方法一, 需要一定的計算機底層的知識才能解釋說通. 方法一和方法二比較: 我自己一開始的時候思考利用方法一這樣的結(jié)論去判斷溢出(overflow), 但是我心里其實不放心, 因為方法一的前提是”兩個正整數(shù)相加溢出的充要條件是符號位變成1, 也就是結(jié)果變成了負(fù)數(shù)”, 這樣的結(jié)論或者事實對于我或者一般人來講其實并不是那么的直觀或者理所當(dāng)然, 當(dāng)然了我自己又用那個一個byte的例子試著去解釋, 結(jié)論還是正確的, ?所以方法一相比較方法二而言并不直觀. 另一方面有說法說是溢出的時候結(jié)果其實不確定, 上面在方法一里面我們的分析只是理論上的分析, 編譯器有可能做出相關(guān)的優(yōu)化或者對溢出結(jié)果做出調(diào)整, 那么可能就出現(xiàn)未定義的行為了, 所以綜上所述, 方法二應(yīng)該是比較更為安全和合理并且更為直觀的首選檢測整數(shù)相加溢出(overflow)的方法. 更新: 感謝網(wǎng)友Stanley的留言, 提供了第三種方法的檢測, 其實也就是方法一的bit operation版本, 通過位操作, 我們可以判斷求和結(jié)果x是否與a和b還同號, 如果同時不同號(也就是sign bit不相同了), 那我們就相當(dāng)于檢測到了溢出. Stanley的版本稍微反了反, 我認(rèn)為應(yīng)該是下面這種情況才是溢出, 如有錯誤, 敬請指正. Thanks! x = a + b; if ((x^a) < 0 && (x^b) < 0) { ? ? //overflow, do something } 結(jié)束語: 本文主要討論了如何判斷整型相加溢出(overflow)的問題, 主要總結(jié)了整型相加溢出(overflow)的原因, 并給出了兩種檢測整型相加溢出(overflow)的方法, 方法一基于計算結(jié)果的正負(fù), 方法二基于把加法轉(zhuǎn)化為減法. 本文同時給出了兩種方法的比較, 并且指出方法二應(yīng)該是首選的檢測方法. |
main函數(shù)的返回值可以在cmd下echo %errorlevel%得到
連接
一個典型的連接(鏈接)實例: add.h #ifndef ADD_H #define ADD_H int add(int, int); #endifadd.c#include "add.h" #include <stdio.h>int add(int x, int y) {return (x + y); }test.c
#include "add.h"int main(void) {printf("%d\n", add(10, 20));return 0; } extern int x;//是聲明,不是定義 這就顯式的說明了x的存儲空間是在程序的其他地方分配的。
所謂的函數(shù)鏈接錯誤:1、找到個一個函數(shù)的兩個實現(xiàn)。 2、一個實現(xiàn)也沒有找到。
庫函數(shù)
FILE *fp;fp = fopen("test.txt", "r+");
為了保持與過去不能同時進行讀寫操作的程序的向下兼容性,一個輸入操作不能隨后直接緊跟著一個輸出操作, 反之亦然。
根本原因就是文件指針的移動問題,使用fseek可以移動文件指針,實現(xiàn)讀寫同時進行的操作。
預(yù)處理器
宏:只是對程序的文本起作用,即簡單的替換。 #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif#ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif 優(yōu)點:普通函數(shù)的調(diào)用涉及空間的申請,參數(shù)入棧、出棧,這樣必然帶來系統(tǒng)開銷。而宏沒有函數(shù)調(diào)用的開銷 缺點:由于只是簡單的文本替換,沒有類型檢查,很容易形成看似正確實則錯誤的代碼。 c++的inline函數(shù)的涉及必然也是想保留宏的優(yōu)點而擯棄宏的缺點。 1、不能忽視宏定義中的空格。 2、宏不是函數(shù)。 3、使用時最好把每個參數(shù)都用括號括起來。 4、宏不是類型定義,定義新類型最好使用typedef。 assert宏的正確定義: #define assert(e) \((void)((e) || _assert_error(__FILE__, __LINE__)))__FILE__, __LINE__是內(nèi)建于C語言預(yù)處理器中的宏,它們會被擴展為所在文件的文件名和所處代碼行的行號。轉(zhuǎn)載于:https://www.cnblogs.com/lgh1992314/p/6616389.html
總結(jié)
以上是生活随笔為你收集整理的C陷阱与缺陷学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: logstash 利用drop 丢弃过滤
- 下一篇: 二叉树经典题之二叉树最近公共祖先(Lee