c語言中一些會遇到的問題

小寫字母轉換爲大寫字母的方法就是將小寫字母的 ASCII 碼值減去 32

1.字符串中末尾是會帶有'\0',也會佔用一個字節。
2.strlen不會統計'\0'
//c語言中的assert函數
//expression -- 這可以是一個變量或任何 C 表達式。如果 expression 爲 TRUE,assert() 不執行任何動作。如果 expression 爲 FALSE,assert() 會在標準錯誤 stderr 上顯示錯誤消息,並中止程序執行。
void assert(int expression);
//標準的strcpy函數
char * strcpy(char * str1,const char * str2)//const跳過char直接修飾str2,不能通過str2修飾str2所指向的值。
{
	char * address=str1;
	while(*str2!='\0')
	{
		assert( (strDest != NULL) && (strSrc != NULL) );//如果裏面是true,則不執行,否則報錯,終止程序運行
		*str1++=*str2++;
	}
	return address;//方便鏈式操作
}

//通過子函數開闢內存空間
int GetMemory( char **p, int num )
{
 *p = (char *) malloc( num );
 if(*p==NULL)//判斷內存申請是否成功?
 {return 0;}
 else return 1;
}
void Test( void )
{
 char *str = NULL;
 GetMemory( &str, 100 );
 strcpy( str, "hello" ); 
 printf( str );
 free(str);
} 

// 關於if比較,幾種不同類型的變量的與0值的判斷
 BOOL型變量:if(!var)
 int型變量: if(var==0)
 float型變量:
 const float EPSINON = 0.00001;
 if ((x >= - EPSINON) && (x <= EPSINON)
 指針變量:  if(var==NULL)

//防止宏定義的坑

//關於頭文件
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif 
的作用是防止被重複引用。

//c++重載
 作爲一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在symbol庫中的名字與C語言的不同。例如,假設某個函數的原型爲: 
void foo(int x, int y); 
  該函數被C編譯器編譯後在symbol庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字。_foo_int_int這樣的名字包含了函數名和函數參數數量及類型信息,C++就是考這種機制來實現函數重載的。
  爲了實現C和C++的混合編程,C++提供了C連接交換指定符號extern "C"來解決名字匹配問題,函數聲明前加上extern "C"後,則編譯器就會按照C語言的方式將該函數編譯爲_foo,這樣C語言中就可以調用C++的函數了。

//memcpy函數
void *memcpy(void *str1, const void *str2, size_t n)
str1 -- 指向用於存儲複製內容的目標數組,類型強制轉換爲 void* 指針。
str2 -- 指向要複製的數據源,類型強制轉換爲 void* 指針。
n -- 要被複制的字節數。(非常需要注意,這個字節數,一個int 4個字節)
memcpy(a + 3, a, 5*sizeof(int));//正確的拷貝方法

//初始化數組等等,初始值是0 
memset(str, 0, sizeof(str));  //只能寫sizeof(str), 不能寫sizeof(p)

//volatile關鍵字 在程序編譯的過程中起到作用
一個變量也許會被後臺程序改變,關鍵字 volatile 是與 const 絕對對立的。它指示一個變量也許會被某種方式修改,這種方式按照正常程序流程分析是無法預知的(例如,一個變量也許會被一箇中斷服務程序所修改)。這個關鍵字使用下列語法定義:
變量如果加了 volatile 修飾,則會從內存重新裝載內容,而不是直接從寄存器拷貝內容。 
volatile應用比較多的場合,在中斷服務程序和cpu相關寄存器的定義。

volatile 用於相關寄存器定義(不會被編譯器優化)
#define GPC1CON *((volatile unsigned int*)0xE0200080)
GPC1CON 爲寄存器名稱、0xE0200080 爲寄存器地址、(volatile unsigned int*) 爲強制類型轉換
是作爲指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。
volatile 可以保證對特殊地址的穩定訪問。
volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取,因而編譯器生成的彙編代碼會重新從i的地址讀取數據放在 b 中。而優化做法是,由於編譯器發現兩次從 i讀數據的代碼之間的代碼沒有對 i 進行過操作,它會自動把上次讀的數據放在 b 中。而不是重新從 i 裏面讀。這樣以來,如果 i是一個寄存器變量或者表示一個端口數據就容易出錯

寄存器地址爲什麼要加 volatile 修飾呢?
因爲,這些寄存器裏面的值是隨時變化的
由於訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化
使用領域
1、並行設備的硬件寄存器
2、一箇中斷服務子程序中會訪問到的非自動變量
3、多線程應用中被幾個任務共享的變量
一個參數既可以是const還可以是volatile嗎,可以,例如只讀的狀態寄存器。它是 volatile 因爲它可能被意想不到地改變。它是 const 因爲 程序不應該試圖去修改它。

extern關鍵字先聲明一下num變量,告訴編譯器num這個變量是存在的,但是不是在這之前聲明的,你到別的地方找找吧,果然,這樣就可以順利通過編譯啦。但是你要是想欺騙編譯器也是不行的,比如你聲明瞭extern int num;但是在後面卻沒有真正的給出num變量的聲明,那麼編譯器去別的地方找了,但是沒找到還是不行的。(是程序鏈接階段才起到作用)

//判斷計算機是小端模式還是大端模式
    union UN
    {
        char c[4];
        int i;//共用四個字節,
    }un;
    un.i = 1;
        if (un.c[0] == 1)//通過數組的地地址,來判斷機器是大端模式還是小端模式。
            return 0;//小端
        else 
            return 1;//大端


typedef struct tagWaveFormat
{ 
 char cRiffFlag[4]; 
 UIN32 nFileLen; 
 char cWaveFlag[4]; 
 char cFmtFlag[4]; 
 char cTransition[4]; 
 UIN16 nFormatTag ; 
 UIN16 nChannels; 
 UIN16 nSamplesPerSec; 
 UIN32 nAvgBytesperSec; 
 UIN16 nBlockAlign; 
 UIN16 nBitNumPerSample; 
 char cDataFlag[4]; 
 UIN16 nAudioLength; 

} WAVEFORMAT; 
假設WAV文件內容讀出後存放在指針buffer開始的內存單元內,則分析文件格式的代碼很簡單,爲:
WAVEFORMAT waveFormat;
memcpy( &waveFormat, buffer,sizeof( WAVEFORMAT ) ); 

//遞歸法實現1+2+。。。.+n
int addsum(int n)
{
	if(n>0){
	return n+addsum(n-1);
	}
}
gcc編譯器
gcc -o 直接output生成可執行程序
    -v 生成很多信息
    -c 只編譯源文件,而不進行鏈接,因此,對於鏈接中的錯誤是無法發現的(func_b() 函數並沒有定義,所以在鏈接時會產生錯誤(編譯時不會產生錯誤))
    -E 生成預處理文件
    -S 生成彙編文件
    -I 手動鏈接庫
    -L 鏈接庫的地址
    -fPIC 生成動態鏈接庫

預處理錯誤
	include錯誤
	define
	
include的“”和<>的區別?
“”優先在本工程項目中找頭文件,<>默認先在turboc 系統目錄中的include文件夾中找頭文件系統庫。


#error命令是C/C++語言的預處理命令之一,當預處理器預處理到#error命令時將停止編譯並輸出用戶自定義的錯誤消息。
語法:
#error 用戶自定義的錯誤消息
舉例:
#if STDC_VERSION != 199901L
#error Not C99
#endif

關鍵字static的作用是什麼?

這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用

  1. 在函數體,一個被聲明爲靜態的變量在這一函數被調用過程中維持其值不變。
  2. 在模塊內(但在函數體外),一個被聲明爲靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
  3. 在模塊內,一個被聲明爲靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用。

const意味着"只讀"

int (* arr )[3]; //arr是一個指向包含3個int元素的數組的指針變量
struct Student p_struct; //結構體類型的指針
int(p_func)(int,int); //指向返回類型爲int,有2個int形參的函數的指針
int
p_pointer; //指向 一個整形變量指針的指針
//取地址
int num = 97;
int* p_num = #
//
int arr[3] = {1,2,3};
int (*p_arr)[3] = &arr;
//
int add(int a , int b)
{
return a + b;
}
int (*fp_add)(int ,int ) = add;

*p_age = 20; //通過指針修改指向的內存數據

//指針之間的賦值
int* p1 = & num;
int* p3 = p1;

//空指針
//指向空,或者說不指向任何東西。在C語言中,我們讓指針變量賦值爲NULL表示一個空指針,而C語言中,NULL實質是 ((void*)0) , 在C++中,NULL實質是0。
//換種說法:任何程序數據都不會存儲在地址爲0的內存塊中,它是被操作系統預留的內存塊。

//錯誤
int*p;
*p = 10; //Oops! 不能對一個未知的地址解地址

//void*類型指針 不知道類型的指針
//p->member 等價於 (*p).member

//改變數值
void change(int* pa)
{
(pa)++; //因爲傳遞的是age的地址,因此pa指向內存數據age。當在函數中對指針pa解地址時,
//會直接去內存中找到age這個數據,然後把它增1。
}
int main(void)
{
int age = 19;
change(&age);
printf(“age = %d\n”,age); // age = 20
return 0;
}
//數值交換
//正確的寫法:通過指針
void swap_ok(int
pa,int*pb)
{
int t;
t=*pa;
*pa=*pb;
*pb=t;
}
//指針函數
void echo(const char *msg)
{
printf("%s",msg);
}
int main(void)
{
void(p)(const char) = echo; //函數指針變量指向echo這個函數
p("Hello "); //通過函數的指針p調用函數,等價於printf("Hello ")
return 0;
}
//const和指針
int a = 1;

int const *p1 = &a;        //const後面是*p1,實質是數據a,則修飾*p1,通過p1不能修改a的值
const int*p2 =  &a;        //const後面是int類型,則跳過int ,修飾*p2, 效果同上
int* const p3 = NULL;      //const後面是數據p3。也就是指針p3本身是const .
const int* const p4 = &a;  // 通過p4不能改變a 的值,同時p4本身也是 const
int const* const p5 = &a;  //效果同上

下面是volatile變量的幾個例子:每次強制從內存中讀,而不是寄存器和cache中讀。

  1. 並行設備的硬件寄存器(如:狀態寄存器)

  2. 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)

  3. 多線程應用中被幾個任務共享的變量

  4. 一個參數既可以是const還可以是volatile嗎?解釋爲什麼。只讀狀態寄存器。

  5. 一個指針可以是volatile 嗎?解釋爲什麼。一個例子是當一箇中服務子程序修該一個指 向一個buffer的指針時。

  6. 下面的函數有什麼錯誤:
    int square(volatile int *ptr)
    {
    return *ptr * *ptr;//本來是想着返回這個的平方的,但是volatile修飾可能隨時都會被修改,和預期的值可能會有區別。
    }

要求設置一絕對地址爲0x67a9的整型變量的值爲0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務。
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一個較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;

在linux下會有兩種IO,一種是同步IO另外一種是異步IO,內存映射IO等
預讀
預讀是指採用預讀算法在沒有系統的IO請求的時候事先將數據從磁盤中讀入到緩存中,然後在系統發出讀IO請求的時候,就會實現去檢查看看緩存裏面是否存在要讀取的數據,如果存在(即命中)的話就直接將結果返回,這時候的磁盤不再需要尋址、旋轉等待、讀取數據這一序列的操作了,這樣是能節省很多時間的;如果沒有命中則再發出真正的讀取磁盤的命令去取所需要的數據。
緩存的命中率跟緩存的大小有很大的關係,理論上是緩存越大的話,所能緩存的數據也就越多,這樣命中率也自然越高,當然緩存不可能太大,畢竟成本在那兒呢。如果一個容量很大的存儲系統配備了一個很小的讀緩存的話,這時候問題會比較大的,因爲小緩存緩存的數據量非常小,相比整個存儲系統來說比例非常低,這樣隨機讀取(數據庫系統的大多數情況)的時候命中率也自然就很低,這樣的緩存不但不能提高效率(因爲絕大部分讀IO都還要讀取磁盤),反而會因爲每次去匹配緩存而浪費時間。
執行讀IO操作是讀取數據存在於緩存中的數量與全部要讀取數據的比值稱爲緩存命中率(Read Cache Hit Radio),假設一個存儲系統在不使用緩存的情況下隨機小IO讀取能達到150IOPS,而它的緩存能提供10%的緩存命中率的話,那麼實際上它的IOPS可以達到150/(1-10%)=166。
回寫
要先說一下,用於回寫功能的那部分緩存被稱爲寫緩存(Write Cache)。在一套寫緩存打開的存儲中,操作系統所發出的一系列寫IO命令並不會被挨個的執行,這些寫IO的命令會先寫入緩存中,然後再一次性的將緩存中的修改推到磁盤中,這就相當於將那些相同的多個IO合併成一個,多個連續操作的小IO合併成一個大的IO,還有就是將多個隨機的寫IO變成一組連續的寫IO,這樣就能減少磁盤尋址等操作所消耗的時間,大大的提高磁盤寫入的效率。
讀緩存雖然對效率提高是很明顯的,但是它所帶來的問題也比較嚴重,因爲緩存和普通內存一樣,掉電以後數據會全部丟失,當操作系統發出的寫IO命令寫入到緩存中後即被認爲是寫入成功,而實際上數據是沒有被真正寫入磁盤的,此時如果掉電,緩存中的數據就會永遠的丟失了,這個對應用來說是災難性的,目前解決這個問題最好的方法就是給緩存配備電池了,保證存儲掉電之後緩存數據能如數保存下來。
和讀一樣,寫緩存也存在一個寫緩存命中率(Write Cache Hit Radio),不過和讀緩存命中情況不一樣的是,儘管緩存命中,也不能將實際的IO操作免掉,只是被合併了而已。
控制器緩存和磁盤緩存除了上面的作用之外還承當着其他的作用,比如磁盤緩存有保存IO命令隊列的功能,單個的磁盤一次只能處理一個IO命令,但卻能接收多個IO命令,這些進入到磁盤而未被處理的命令就保存在緩存中的IO隊列中。
RAID(Redundant ArrayOf Inexpensive Disks)
如果你是一位數據庫管理員或者經常接觸服務器,那對RAID應該很熟悉了,作爲最廉價的存儲解決方案,RAID早已在服務器存儲中得到了普及。在RAID的各個級別中,應當以RAID10和RAID5(不過RAID5已經基本走到頭了,RAID6正在崛起中,看看這裏瞭解下原因)應用最廣了。下面將就RAID0,RAID1,RAID5,RAID6,RAID10這幾種級別的RAID展開說一下磁盤陣列對於磁盤性能的影響,當然在閱讀下面的內容之前你必須對各個級別的RAID的結構和工作原理要熟悉才行,這樣才不至於滿頭霧水,推薦查看wikipedia上面的如下條目:RAID,Standard RAID levels,Nested RAID levels。

異步IO和同步IO區別
如果是同步IO,當一個IO操作執行時,應用程序必須等待,直到此IO執行完,相反,異步IO操作在後臺運行,
IO操作和應用程序可以同時運行,提高系統性能,提高IO流量; 在同步文件IO中,線程啓動一個IO操作然後就立即進入等待狀態,直到IO操作完成後才醒來繼續執行,而異步文件IO中,
線程發送一個IO請求到內核,然後繼續處理其他事情,內核完成IO請求後,將會通知線程IO操作完成了。

大小端模式
int main(void)
{
int a = 0x1234;
char b = ((char)&a);
if (b == 0x12)
printf(“BIGEND\n”);
if (b == 0x34)
printf(“littleEND\n”);
system(“PAUSE”);
return 0;
}

#多線程
int a=10;
子進程會複製一份
多線程會公用
pthread.h頭文件
編譯要加-lpthread

//創建線程
pthread_create()
main函數退出後,不會等待子線程。
pthread_join 等待線程結束
兩個線程運行速度不一致的。
s++,三步操作,讀s,s+1,寫回s
鎖只有一個,一個進程用了鎖,那麼另外的線程就必須等待別的進程解鎖以後,才能加鎖,完成相應的代碼。
鎖機制
pthread_mutex_t writable[100]; //lock
pthread_mutex_init(&writable[i], NULL); //鎖的初始化
pthread_mutex_lock(&writable[i]); //加鎖
… //臨界區
pthread_mutex_unlock(&writable[i]); //解鎖

假共享,在多核多線程運算中。

指針和引用的區別
(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。
(2)可以有const指針,但是沒有const引用;
(3)指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值可以爲空,但是引用的值不能爲NULL,並且引用在定義的時候必須初始化;
(5)指針的值在初始化後可以改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了。
(6)"sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;
(7)指針和引用的自增(++)運算意義不一樣;引用是值++;比如b是引用a[0]的,++表示a[0]的值++從0變爲1;

野指針出現的情況:
1.指針未初始化
指針變量在定義時不會自動初始化成空指針,而是隨機的一個值,可能指向任意空間,這就使得該指針成爲野指針。因此指針在初始化時要麼指向一個合理的地址,要麼初始化爲NULL。
2.指針指向的變量被free或delete後沒有置爲NULL
在調用free或delete釋放空間後,指針指向的內容被銷燬,空間被釋放,但是指針的值並未改變,仍然指向這塊內存,這就使得該指針成爲野指針。因此在調用free或 delete之後,應將該指針置爲NULL。
3.指針操作超過所指向變量的生存期
當指針指向的變量的聲明週期已經結束時,如果指針仍然指向這塊空間,就會使得該指針成爲野指針。這種錯誤很難防範,只有養成良好的編程習慣,才能避免這類情況發生。
注意:野指針只能避免而無法判斷
無法判斷一個指針是否爲野指針,因爲野指針本身有值,指向某個內存空間,只是這個值是隨機的或錯誤的。而空指針具有特殊性和確定性,可以進行判斷,因此要避免在程序中出現野指針。
危害:
1、指向不可訪問的地址
危害:觸發段錯誤。
2、指向一個可用的,但是沒有明確意義的空間
危害:程序可以正確運行,但通常這種情況下,我們就會認爲我們的程序是正確的沒有問題的,然而事實上就是有問題存在,所以這樣就掩蓋了我們程序上的錯誤。
3、指向一個可用的,而且正在被使用的空間
危害:如果我們對這樣一個指針進行解引用,對其所指向的空間內容進行了修改,但是實際上這塊空間正在被使用,那麼這個時候變量的內容突然被改變,當然就會對程序的運行產生影響,因爲我們所使用的變量已經不是我們所想要使用的那個值了。通常這樣的程序都會崩潰,或者數據被損壞。

什麼原因導致的內存泄露的問題呢?
1.分配完內存之後忘了回收;
2.程序Code有問題,造成沒有辦法回收;
3.某些API函數操作不正確,造成內存泄漏。

定位錯誤:
遇到OOM,你首先要確定他是由於什麼原因引起的?是因爲堆空間設置太小引起還是因爲內存泄露引起。實際上,內存泄露的問題可以通過增大堆空間暫時得到解決,但是他不是長久之計。
mtrace

#include <stdio.h>
 
int main()
{
        setenv("MALLOC_TRACE", "taoge.log", "1");
        mtrace();
 
        int *p = (int *)malloc(2 * sizeof(int));
 
        return 0;
}

gcc -g test.c,運行可定位內存溢出的位置。

發佈了28 篇原創文章 · 獲贊 20 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章