2013-07-13 75 views
6

據說我們可以寫多個聲明但只有一個定義。現在,如果我實現我自己的strcpy函數有相同的原型:實現一個新的strcpy函數重新定義了庫函數strcpy?

char * strcpy (char * destination, const char * source); 

然後我不能重新定義現有的庫函數?不應該顯示一個錯誤?或者它是否與目標代碼形式提供的庫函數有關?

編輯:在我的機器上運行下面的代碼說「分段錯誤(核心轉儲)」。我在linux上工作,編譯時沒有使用任何標誌。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

char *strcpy(char *destination, const char *source); 

int main(){ 
    char *s = strcpy("a", "b"); 
    printf("\nThe function ran successfully\n"); 
    return 0; 
} 

char *strcpy(char *destination, const char *source){ 
    printf("in duplicate function strcpy"); 
    return "a"; 
} 

請注意,我沒有試圖實現該功能。我只是試圖重新定義一個功能並要求後果。

編輯2: 通過Mats應用建議的更改後,該程序不再給出分段故障,但我仍在重新定義該函數。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

char *strcpy(char *destination, const char *source); 

int main(){ 
    char *s = strcpy("a", "b"); 
    printf("\nThe function ran successfully\n"); 
    return 0; 
} 

char *strcpy(char *destination, const char *source){ 
    printf("in duplicate function strcpy"); 
    return "a"; 
} 
+1

你不能做到這一點,那將崩潰 – DGomez

+0

這可能與:如何以取代C標準庫函數](http://stackoverflow.com/questions/9107259/how-to-replace-c-standard-library-functioin)。 –

+1

@DGomez在我的機器上,程序沒有崩潰。 –

回答

11

C11(ISO/IEC 9899:201X)§7.1.3保留的標識符

- 在任何以下小節(包括未來庫 方向)中的每個宏名稱被保留用作指定是否包含任何關聯標題;除非另有明確說明。

- 在以下任何子條款中(包括未來庫指示的 將始終保留)用作具有外部 鏈接的標識符。

- 以任何以下小節(包括 未來圖書館方向)的列出的文件範圍每個標識符被保留用於爲宏名稱和如在相同的名稱空間 文件範圍的標識符,如果它的任何包括相關聯的標題。

如果程序在保留的上下文中聲明或定義了標識符,或者將保留的標識符定義爲宏名稱,則行爲是未定義的。請注意,這並不意味着你不能這麼做,如this post所示,它可以在gcc和glibc中完成。

的glibc §1.3.3 Reserved Names proveds更清晰的理由:

所有庫類型,宏變量和函數來自ISO C標準的無條件保留的名稱;您的程序可能不會重新定義這些名稱。如果您的程序明確包含定義或聲明它們的頭文件,則所有其他庫名都是保留的。這些限制有以下幾個原因:例如,如果您使用名爲exit的函數完成與標準出口函數完全不同的操作,其他讀取您代碼的人可能會感到非常困惑。避免這種情況有助於使您的程序更易於理解並有助於模塊化和可維護性。

它避免了用戶意外重新定義由其他庫函數調用的庫函數的可能性。如果允許重新定義,那些其他功能將無法正常工作。

它允許編譯器對這些函數的調用進行任何特殊的優化,而不會有可能被用戶重新定義。一些圖書館設施,如用於處理可變參數(參見變量函數)和非本地出口(參見非本地出口)的庫實際上需要C編譯器的大量合作,並且關於實現時,編譯器可能更容易將它們視爲語言的內置部分。

+2

所以我們在這裏處理的是「未定義的行爲[u] r」,我猜 –

+0

@WalterTross從我所理解的是, –

+0

您的答案_together_ with one by @MatsPetersson作出正確答案如果我是OP,我不知道哪一個可以接受。 –

7

這是幾乎可以肯定,因爲你傳遞一個目的地是一個「字符串文字」。

char * s = strcpy(「a」,「b」);

隨着編譯器知道「我可以做strcpy內聯」,所以你的函數永遠不會被調用。

您試圖將"b"複製到字符串文字"a"上,那樣做不起作用。

做一個char a[2];strcpy(a, "b");,它會運行 - 它可能不會打電話給你strcpy功能,因爲編譯器內聯小strcpy即使你沒有優化可用做。

+0

我確認了簡單的gcc調用我的'strcpy'只有當第二個參數是一個變量,_not_如果它是一個字符串文字 –

2

你的問題是誤導。

您看到的問題與重新實現庫函數無關。

您只是在嘗試寫入不可寫內存,即字符串字面值爲a的內存。

說得簡單,下面的程序給我的機器上分段錯誤(與gcc 4.7.3編譯,沒有任何標誌):

#include <string.h> 

int main(int argc, const char *argv[]) 
{ 
    strcpy("a", "b"); 
    return 0; 
} 

但隨後,爲什麼如果你調用一個版本的strcpy分段錯誤(你的)不寫非可寫內存?只是因爲你的功能沒有被調用。

如果用-S標誌編譯代碼,並看看編譯器生成它的彙編代碼,就不會有callstrcpy(因爲編譯器「內聯」這一號召,唯一相關的呼叫你可以從main看到,是致電puts)。

.file "test.c" 
    .section .rodata 
.LC0: 
    .string "a" 
    .align 8 
.LC1: 
    .string "\nThe function ran successfully" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movw $98, .LC0(%rip) 
    movq $.LC0, -8(%rbp) 
    movl $.LC1, %edi 
    call puts 
    movl $0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size main, .-main 
    .section .rodata 
.LC2: 
    .string "in duplicate function strcpy" 
    .text 
    .globl strcpy 
    .type strcpy, @function 
strcpy: 
.LFB3: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movq %rdi, -8(%rbp) 
    movq %rsi, -16(%rbp) 
    movl $.LC2, %edi 
    movl $0, %eax 
    call printf 
    movl $.LC0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE3: 
    .size strcpy, .-strcpy 
    .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" 
    . 

我覺得俞灝的回答有一個很好的解釋,從標準的報價:

是 來自ISO C的所有庫類型,宏,變量和函數的名稱標準無條件保留;您的 程序可能不會重新定義這些名稱。如果您的程序明確包含 定義或聲明它們的頭文件,則所有其他庫名稱均爲 。有幾個原因,這些 限制:

[...]

它可以讓編譯器做任何特殊的優化它調用這些函數高興 ,沒有他們可能 已重新定義的可能性由用戶。

+0

嘿,我已經糾正了代碼。請參閱EDIT2。 –

+0

@NikunjBanka我已經用一些彙編代碼更新了答案:) –

1

您的例子可以這樣操作:(帶的strdup

char *strcpy(char *destination, const char *source); 

int main(){ 
    char *s = strcpy(strdup("a"), strdup("b")); 
    printf("\nThe function ran successfully\n"); 
    return 0; 
} 

char *strcpy(char *destination, const char *source){ 
    printf("in duplicate function strcpy"); 
    return strdup("a"); 
} 

輸出:

in duplicate function strcpy 
    The function ran successfully 
1

解釋此規則的方式是,你不能有多個定義一個函數結束於最終的鏈接對象(可執行文件)。所以,如果鏈接中包含的所有對象只有一個函數的定義,那麼你就很好。牢記這一點,請考慮以下情況。

  1. 假設您重新定義了某個庫中定義的函數somefunction()。你的函數在main.c(main.o)中,在函數庫中的函數是一個名爲someobject.o的對象(在libray中)。請記住,在最終鏈接中,鏈接程序僅在庫中查找未解決的符號。因爲somefunction()已經從main.o中解析出來了,鏈接器甚至不會在庫中查找它,也不會引入someobject.o。最終的鏈接只有一個函數的定義,事情都很好。
  2. 現在想象一下,在someobject.o中定義另一個符號anotherfunction(),您也碰巧打電話給它。鏈接器將嘗試解析someobject.o中的另一個函數(),並將其從庫中提取出來,它將成爲最終鏈接的一部分。現在,在最後一個鏈接中有兩個somefunction()的定義 - 一個來自main.o,另一個來自someobject.o,鏈接器會拋出一個錯誤。
4

把試圖修改不可修改內存的問題放在一邊,請記住,你在形式上不允許重新定義標準庫函數。

但是,在某些實現中,您可能會注意到爲標準庫函數提供另一個定義不會觸發通常的「多重定義」錯誤。發生這種情況是因爲在這種實現中,標準庫函數被定義爲所謂的「弱符號」。舉個例子,GCC標準庫是已知的。

這樣做的直接後果是,當您通過外部鏈接定義標準庫函數的「版本」時,您的定義會覆蓋整個程序的「弱」標準定義。您會注意到,不僅您的代碼現在會調用您的函數版本,還會將所有預編譯的[第三方]庫中的所有類都分發給您的定義。它的目的是作爲一項功能,但您必須意識到這一點,以避免無意中「使用」此功能。

你可以閱讀一下,對一個例子

How to replace C standard library function ?

執行此功能不違反語言規範,因爲它不受管轄的未定義行爲未知的區域內運行任何標準要求。

當然,使用某些標準庫函數的內部/內聯實現的調用不會受到重定義的影響。

1

我用這一個頻繁:

​​

,你也可以做strncpy()函數只需修改一行

void my_strncpy(char *dest, char *src, int n) 
{ 
    int i; 

    i = 0; 
    while (src[i] && i < n) 
    { 
     dest[i] = src[i]; 
     i++; 
    } 
    dest[i] = '\0'; 
}