在一個夜深人靜的晚上,有一個讀者給我發了一個C語言題目。他問我,發哥,幫我看看這個代碼有什麼問題。我看了代碼之後,心裡一陣恐慌。我自認為我不是C語言高手。但我確實是一個喜歡解決問題的男人。就是在這樣的背景驅使下,我寫下了這篇文章。
char *str1 = "hello";
char str2[] = "hello";我們看這兩個定義。我們說這個是定義而不是聲明,是因為定義在內存裡面分配了房子。而聲明,只給了個房產證卻不給你分房子。
str1 是 char *類型 。它是一個指針,這個指針指向一個字符串。
str2 是 char [] 類型。它是一個數組,他代表了這堆內存空間。
「hello」字符串在內存中是這樣存放的
我之前寫過一個不同變量地址分配在內存不同區域的文章,有不清晰的可以再回去看看。
str1 str3都是指向字符串的指針,而且這個字符串是保存在字符串常量區的。這個常量區裡面的東西是不能被修改的。編譯器讓他們指向了同一個地址。這個地址保存的東西是 「hello」這個字符串。
大家看看下面這個代碼有什麼問題?
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main(void)
{
char *str1 = "hello";
char *str3 = "hello";
char str2[] = "hello";
memcpy(str3,"worldtest",strlen("worldtest")+1);
printf("str1:%s str3:%s str2:%s\n",str1,str3,str2);
str3 = "world";
printf("str1:%s str3:%s str2:%s\n",str1,str3,str2);
printf("hello,world\n");
return (0);
}memcpy嘗試向一個非法的地址拷貝東西,這個是不允許的。為什麼說這個地址非法呢?因為字符常量區裡面的內容,只可以讀,不可以寫。
如果改成這樣的呢?應該輸出什麼結果呢?
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main(void)
{
char *str1 = "hello";
char *str3 = "hello";
char str2[] = "hello";
//memcpy(str3,"worldtest",strlen("worldtest")+1);
printf("str1:%s str3:%s str2:%s\n",str1,str3,str2);
str3 = "world";
printf("str1:%s str3:%s str2:%s\n",str1,str3,str2);
printf("hello,world\n");
return (0);
}我之前在文章裡面討論一個問題,我們說指針的時候,要說指針變量。指針變量保存的內容是一個地址。既然是變量,那麼保存的地址是可以變化的。只要類型符合。都可以保存。
同樣的,在上面的例子中,如果我們嘗試這樣
str1[1] = 'a';這樣也是錯誤的。這樣也是寫操作了非法的地址。
試試下面這段代碼
#include <stdio.h>
int main(){
char* str1="Hello";
printf("\nstr1: %s, address: %p, sizeof(str1): %u", str1, str1, sizeof(str1));
str1 = "world";
printf("\nstr1: %s, address: %p, sizeof(str1): %u", str1, str1, sizeof(str1));
return 1;
}輸出
str1: Hello, address: 0000000000404000, sizeof(str1): 8
str1: world, address: 0000000000404031, sizeof(str1): 8
--
Process exited after 0.0226 seconds with return value 1
請按任意鍵繼續. . .通過賦值運算後,str1的值也發生了改變。
但是str2情況會不一樣,str2是一個數組。
既然是數組,我們看看這段小代碼
#include<stdio.h>
int main(){
char str2[] = "hello";
printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
str2[2] = 'A';
printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
strcpy(str2, "world");
printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
return 1;
}輸出日誌
str2: hello, address: 000000000062FE10, sizeof(str2): 6
str2: heAlo, address: 000000000062FE10, sizeof(str2): 6
str2: world, address: 000000000062FE10, sizeof(str2): 6
--
Process exited after 0.04063 seconds with return value 1
請按任意鍵繼續. . .送一個圖
晚上回來我寫了一個小程序。大家看看
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
const int a = 1;
const int a1 = 1;
char * s = "hello";
int main()
{
const int b = 2;
const int b1 = 2;
char * s1 = "hello";
printf("s:%p s1:%p\n",s,s1);
printf("a:%p a1:%p b:%p b1:%p\n",&a,&a1,&b,&b1);
return 1;
}輸出如下:
s:0000000000404008 s1:0000000000404008
a:0000000000404000 a1:0000000000404004 b:000000000062FE14 b1:000000000062FE10
--
Process exited after 0.03901 seconds with return value 1
請按任意鍵繼續. . .可以看到,s,s1,a,a1在一個內存區域。這個內存區域的內容是不允許改變的。如果你對這裡的內存區域賦值,就會出現段錯誤。
但是b和b1這個內存區域大家看看。我們可以寫個小代碼測試一下。
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
const int b = 2;
int main()
{
const int b1 = 2;
int *p = &b1;
printf("b1:%d\n",b1);
*p = 3;
printf("b1:%d\n",b1);
return 1;
}輸出:
b1:2
b1:3
--
Process exited after 0.0403 seconds with return value 1
請按任意鍵繼續. . .但是我們寫成這樣呢?
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
const int b = 2;
int main()
{
const int b1 = 2;
int *p = &b;
printf("b:%d\n",b);
*p = 3;
printf("b:%d\n",b);
return 1;
}輸出:
b:2
--
Process exited after 3.743 seconds with return value 3221225477
請按任意鍵繼續. . .如果放到gcc下,可以看到,執行到代碼
*p = 3;會出現段錯誤。因為訪問了不能訪問的地址。這也就是我們很多時候給空指針賦值出現段錯誤的原因。操作了非法的地址。
好了,就瞎BB這麼多,如果覺得有用,可以留言一起討論下。