2012/02/03

[C/C++]char pointer & sizeof Q&A

Q:
{
    char *string="hello";
    string++;
     *abc='2';
     string--;
     printf("%s",string);
}

char *string="hello" 為什麼不能對指標做加減然後只改某個字?(執行的時候出現了錯誤)

 
A: "hello" 叫做 string literal, 它的大小在編譯器分析你原始碼的時候就知道了. 編譯器在編譯的時候會把這個字串的內容直接放在一個區域裡, 然後把指標的內容設為這個字串第一個字元的位址.
這些完全可以在編譯時搞定, 沒有必要等到執行時才做.
如果你的程式有幾個同樣的字串, 比方說:

void fun1(void)
{  
    char *p = "hello";
    ...
}  
void fun2(void)
{
    char *p2 = "hello";
    printf("hello");
    ...
}
編譯器有可能會把這幾個 "hello" 『合而為一』,也就是說,編譯器只需放一個 "hello", 所有的指標都指向這一個 "hello". 這是語言標準允許的行為.
但如果依照這種講法,為什麼不能對指標做加減然後只改某個字?(執行的時候出現了錯誤), 如果不是這種方法,那它是如何運作的呢?
事實上 String literal 的內容是不可更改的, 這是語言標準的規定. 任何意圖去更改 string literal 的內容都會導致不可預期的結果.
所以

char *p = "hello";

應該視為:

const char *p = "hello";

'p' 指向一個唯讀的記憶體.

Q:

short acc[50];
printf("%d",sizeof(acc));
若編譯器不需要把物件的長度記錄下來, 那為什麼它印的出100 (short=2byte x 50 = 100 byte)? 難不成是程式知道的嗎?

A: 不是程式知道, 而是編譯器知道. sizeof(acc) 是在編譯期計算出來的. sizeof 是個運算子, 不是函式.

Extended:

Note1: 指標p 指向陣列位址, 就可以用 p[i] 代替 *(p+i)
上面這句有點誤差. p[i] 這個語法不侷限於陣列. 事實上, p[i] 和 *(p+i) 在語意上是完全一樣的, p[i] 可以看做是 *(p+i) 的方便寫法. 編譯器會把所有的 p[i] 轉成 *(p+i).

Note2: 指標若沒指定初值,一般要用malloc來配置記憶體
char *st;
st = (char *) malloc(10);

如果是用 C 語言, 那這個 typecast 沒有必要. 在 C 語言裡, 從 void* 轉至任何指標類型或從任何指標類型轉至 void* 都不需要 typecast.
如果編譯器告訴你無法從 void* 轉至 char*, 很可能是因為程式員用了 C++ 編譯器來編譯. 而造成這個錯誤的原因是原始碼檔的 extension 是 .cpp 而不是 .c.

Note3: 再補充一下關於 sizeof 運算子
C99 語言標準新增了一個叫 Variable Length Array (VLA) 的東西, 陣列的大小可在執行時決定:

#include <stdio.h>

void func(int N)
{
    int vla[N];
    for (size_t n = 0; n < N; ++n)
    {
        vla[n] = n * 2;
    }
    printf("sizeof vla = %zu\n", sizeof vla);
    for (size_t n = 0; n < N; ++n)
    {
        printf("vla[%d] = %d\n", n, vla[n]);
    }
}

int main(void)
{
    int N;
    printf("Enter size: ");
    fflush(stdout);
    scanf("%d", &N);
    func(N);
}

如果 sizeof 運算元是個 VLA, 那 sizeof 的運算會延遲到執行期. 但如果運算元不是 VLA, 那它還是在編譯期運算出大小.
Note: VLA 是 C99 才有的, 之前的 C90 不支援這個. C++ 從來也沒有這方面的東西. 所以上面的程式只能在有支援 C99 語言標準的編譯器上編譯.


Note4: 當宣告了一個陣列 int a[100]; 然後再宣告一個變數 比方說 char b; 但程式並不會記憶陣列長度的資訊, 那編譯器是怎麼知道b要放置的記憶體位置?
當你定義一個物件時, 在編譯的時候, 編譯器會撥出足夠大的記憶體來存放這個物件. 對編譯程式來說, 這樣就夠了, 編譯器不需要把物件的長度記錄下來.
"程式知道"跟"編譯器知道"是完全的兩回事. 就好像編譯器知道變數的名字, 但這個資料不會存放在程式裡. 另一方面, 在編譯的時候, 編譯器並不需要知道函式的位址或某個 external 物件的位址, 這些資料是在最後 link 的時候才由 linker 解決.



Note5: 假定程式時常用new 和 delete 配置記憶體, 有沒有可能出現new int[100]時, 明明記憶體足夠, 卻不是連續的窘境?
從程式語言的角度來看, 陣列的元素必須是連續的. 這是語言標準的規定. 但這些記憶體在對照到 physical memory 時是不是連續的則不關程式的事, 這不在程式的控制下, 也不可能知道.

沒有留言: