1 字符串 c++
1.1 字符串基礎 編程
字符串提供命令行參數、環境變量、控制檯輸入、文本文件及網絡連 數組
接,提供外部輸入方法來影響程序的行爲和輸出,這也是程序容易出錯的地方。字符串是一個概念,並非C/C++內置類型,標準C語言庫支持類型爲char的字符串和類型爲wchar_t的寬字符串。 安全
字符串由一個以第一個空(null)字符做爲結束的連續字符序列組成,並 網絡
包含此空字符(因此sizeof和strlen會差1)。一個指向字符串的指針實際指向該字符串的起始字符。目標大小,指sizeof(array)大小,注意與元素個數區分。 ide
數組大小。數組帶來的問題之一是肯定其元素數量,例以下面的例子: 函數
void clear(int array[]) spa
{ 命令行
for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i) 指針
{
array[i] = 0;
}
}
void dowork()
{
int dis[12];
clear(dis);
/* ... */
}
array是一個參數,因此它的類型是指針。所以,sizeof(array)等於sizeof(int*),在x86 32機中,sizeof(array) / sizeof(array[0])計算結果都是1。
字符串字面值:簡而言之就是在雙引號中的值,在C中,字符串字面值的類型是一個char數組,但在C++中,它是一個const char數組。因此在C中能夠修改字面值,可是程序若是試圖去修改,該行爲是未定義的。不要試圖修改字符串字面值,編譯器有時會把多個相同的字符串字面值存儲在相同位置,例如只讀存儲器(ROM)中,看下面例子:
const char *s1 = "abc";
const char *s2 = "abc";
char *s3 = "abc";
char *s4 = "abc";
char s5[] = "abc";
char s6[] = "abc";
比較地址會發現s1,s2,s3,s4相同,用這4個指針去改變字符串字面值是會出問題的。s5,s6值不一樣
字符數組初始化:不要指定一個用字符串字面值初始化的字符數組的界限
const char s[3] = "abc"; //不安全寫法,少一個'\0'
const char s[] = "abc"; //推薦初始化方式
1.2 C++中的字符串
C++標準類模板std::basic_string。簡單來講就是string(basic_string<char>)
和wstring(basic_string<wchar_t>),basic_string的類的模版特化更不容易出現錯誤和安全漏洞,須要強調的是大多數C++字符串對象被視爲不可分割的總體(一般按值傳遞和引用傳遞),內部字符串不必定是以空字符結束(大多數實現是以空字符結尾),C的庫函數都接受以空字符結尾的字符序列指針。
1.3 字符類型
char 是 signed char 仍是 unsigned char 可由編譯器的配置項設定
當char有符號時,由unsigned char[]轉換爲const char *
當char無符號時,由singned char[] 轉換爲const char *
若是不強制轉換會有警告,建議使用普通的char
1.4 字符串的長度
混淆概念容易在C和C++中致使嚴重的錯誤,
wchar_t wide_str1[] = L"0123456789";
wchar_t *wide_str2 = (wchar_t*)malloc(strlen(wide_str1) + 1);
if(wide_str2 == NULL)
{
/*處理錯誤*/
}
free(wide_str2);
wide_str2 = NULL;
對一個以空字符結尾的字節字符串,strlen()統計終止空字節前面的字符數量。然而,寬字符能夠包含空字節,因此計算結果會出問題。
使用wcslen能夠計算寬字符串的大小
wchar_t wide_str1[] = L"0123456789";
wchar_t *wide_str2 = (wchar_t*)malloc(wcslen(wide_str1) + 1);
if(wide_str2 == NULL)
{
/*處理錯誤*/
}
free(wide_str2);
wide_str2 = NULL;
注意此長度沒有乘sizeof(wchar_t),因此仍是不對,下面值最終正確寫法:
wchar_t wide_str1[] = L"0123456789";
wchar_t *wide_str2 = (wchar_t*)malloc((wcslen(wide_str1)+1)*sizeof(wchar_t));
if(wide_str2 == NULL)
{
/*處理錯誤*/
}
free(wide_str2);
wide_str2 = NULL;
2 常見的字符串操做錯誤
2.1 無界字符串複製
void get_y_or_n()
{
char response[8];
puts("Continue? [y] n:");
gets(response);
if(response[0] == 'n')
exit(0);
return;
}
其實gets()函數在C99中以廢棄並在C11中淘汰。它沒有提供方法指定讀入的字符數的限制。這種限制在此函數的以下一致實現中是顯而易見的:
char *gets(char *dest)
{
int c = getchar();
char *p = dest;
while(c != EOF && c != '\n')
{
*p++ = c;
c = getchar();
}
*p = '\0';
return dest;
}
若是輸入超出8個字符,那麼會致使未定義的行爲。不要從一個無界源複製數據到定長數組中,禁止這種方法。
2.1.1 複製和鏈接字符串
例如strcpy(), strcat(), sprintf(), 容易執行無界操做。例如:
int main(int argc, char *argv[])
{
/*argc參數個數,argv參數數組*/
}
當argc大於0,按照慣例,argv[0]指向的字符串是程序名。若argc > 1,則argv[0]~argv[argc-1]引用的就是實際程序參數。
當分配的空間不足以複製一個程序的輸入,就會產生漏洞。攻擊者能夠控制argv[0]的內容
int main(int argc, char *argv[])
{
/*argc參數個數,argv參數數組*/
char prog_name[128];
strcpy(prog_name, argv[0]);
/* ... */
}
輸入一個大於128個字節的字符,棧溢出,即緩衝區溢出漏洞。
標準的寫法應該是:
int main(int argc, char *argv[])
{
/* 不要假設argv[0]不準爲空 */
const char *const name = argv[0]? argv[0] : "";
char *prog_name = (char*)malloc(strlen(name)+1);
if(prog_name != NULL)
{
strcpy(prog_name, name);
}
else
{
/* 復原 */
}
}
其實還有一種方法能夠避免溢出,經過設置域寬能夠消除gets()的缺陷
char buf[12];
std::cin::width(12);
std::cin >> buf;
std::cout << buf << std::endl;
2.2 差一錯誤
簡而言之就是從源字符串拷貝內容到目的字符串,恰好最後的'\0'沒有
拷貝到目的字符串中,在這以後對目的串調用C語言庫的函數可能會出問題,即空字符結尾錯誤,其他的還有字符串階截斷偏差,越界操做等。
2.3 字符串漏洞及其利用
大致上就是緩衝區溢出(詳細的能夠本身網上查,有不少資料詳細介
紹),棧溢出的話,能夠把目標代碼或者數據覆蓋到棧裏面,關於棧爲何會溢出,實際上是由於在編譯後,棧的大小就固定了。這種攻擊方式也稱注入,這裏涉及到彙編以及底層的結構,不作詳細解釋,不過解決方法也有不少,要麼作邊界檢查,要麼動態的分配內存,還有更簡單的那就是直接使用std::basic_string。固然使用string也會出問題,例如迭代器失效。
char input[];
string email;
string::iterator loc = email.begin();
//複製到string對象,同時把";" 轉換成" "
for (size_t i = 0; i < strlen(input); ++i)
{
if(input[i] != ";")
email.insert(loc++, input[i]);
else
email.insert(loc++, ' ');
}
第一次insert以後,loc就已經失效,後面的insert都將產生未定義行爲。正確的寫法應該是
char input[];
string email;
string::iterator loc = email.begin();
//複製到string對象,同時把";" 轉換成" "
for (size_t i = 0; i < strlen(input); ++i)
{
if(input[i] != ";")
loc = email.insert(loc, input[i]);
else
loc = email.insert(loc, ' ');
++loc;
}
固然在編程的時候引用邊界以外的元素會拋出一個異常std::out_of _range。另外std::string.c_str()函數能夠返回一個以空字符結尾的字符,const值,因此調用free()或者delete()會出錯,須要修改則只能修改副本。