据说我们可以编写多个声明但只能编写一个定义。现在,如果我使用相同的原型实现我自己的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";
}
答案 0 :(得分:11)
C11(ISO / IEC 9899:201x)§7.1.3保留标识符
- 以下任何子条款中的每个宏名称(包括未来的库 如果包含任何相关标题,则保留指定用途; 除非另有明确说明。
- 以下任何子条款中包含外部链接的所有标识符(包括 未来的图书馆方向)始终保留用作外部标识符 键。
- 具有文件范围的每个标识符在以下任何子条款中列出(包括 未来的图书馆方向)保留用作宏名称和标识符 如果包含任何相关标头,则文件范围在同一名称空间中。
如果程序在保留它的上下文中声明或定义标识符,或者将保留标识符定义为宏名称,则行为是未定义的。请注意,这并不意味着你不能这样做,正如this post所示,它可以在gcc和glibc中完成。
glibc §1.3.3 Reserved Names证明了一个更明确的理由:
来自ISO C标准的所有库类型,宏,变量和函数的名称都是无条件保留的;您的程序可能不会重新定义这些名称。如果您的程序明确包含定义或声明它们的头文件,则保留所有其他库名。这些限制有几个原因:
如果您使用名为exit的函数执行与标准退出函数完全不同的操作,那么阅读代码的其他人可能会感到非常困惑。防止这种情况有助于使您的程序更易于理解,并有助于模块化和可维护性。
它避免了用户意外重新定义其他库函数调用的库函数的可能性。如果允许重新定义,那些其他功能将无法正常工作。
它允许编译器在调用这些函数时进行任何特殊的优化,而不会被用户重新定义。一些库设施,例如用于处理可变参数(参见Variadic函数)和非本地出口(参见非本地退出)的设施,实际上需要C编译器方面的大量合作,并且相对于在实现中,编译器可能更容易将它们视为语言的内置部分。
答案 1 :(得分:7)
这几乎可以肯定是因为你传入了一个“字符串文字”的目的地。
char * s = strcpy(“a”,“b”);
随着编译器知道“我可以strcpy
内联”,所以你的函数永远不会被调用。
您正在尝试将"b"
复制到字符串文字"a"
上,但这不起作用。
制作一个char a[2];
和strcpy(a, "b");
并且它会运行 - 它可能不会调用您的strcpy
函数,因为编译器会内联小strcpy
,即使您没有有优化可用。
答案 2 :(得分:4)
除了试图修改不可修改的内存之外,请记住,正式不允许重新定义标准库函数。
但是,在某些实现中,您可能会注意到为标准库函数提供另一个定义不会触发通常的“多重定义”错误。发生这种情况是因为在这种实现中,标准库函数被定义为所谓的“弱符号”。例如,GCC标准库就是众所周知的。
这样做的直接后果是,当您使用外部链接定义自己的标准库函数“版本”时,您的定义将覆盖整个程序的“弱”标准定义。您会注意到,不仅您的代码现在调用您的函数版本,而且所有预编译的[第三方]库中的所有类也都会调度到您的定义中。它旨在作为一项功能,但您必须注意它,以避免无意中“使用”此功能。
你可以在这里阅读一下这个例子
How to replace C standard library function ?
该实现的此功能不违反语言规范,因为它在未定义行为的未知区域内运行,不受任何标准要求的约束。
当然,使用内部/内联实现某些标准库函数的调用不会受到重新定义的影响。
答案 3 :(得分:2)
您的问题具有误导性。
您看到的问题与重新实现库函数无关。
您只是在尝试编写不可写内存,即存在字符串文字a
的内存。
简单来说,下面的程序在我的机器上给出了一个分段错误(用gcc 4.7.3
编译,没有标志):
#include <string.h>
int main(int argc, const char *argv[])
{
strcpy("a", "b");
return 0;
}
但是,如果您调用不写不可写内存的strcpy
(您的)版本,为什么分段错误?只是因为你的功能没有被调用。
如果使用-S
标志编译代码并查看编译器为其生成的汇编代码,那么call
将不会strcpy
(因为编译器)已经“内联”了该来电,您可以从主电话中看到的唯一相关电话,是对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标准是无条件保留的;您的 程序可能不会重新定义这些名称。所有其他库名称 如果您的程序明确包含头文件,则保留 定义或声明它们。这有几个原因 限制:
[...]
它允许编译器执行它喜欢的任何特殊优化 在调用这些函数时,它们可能没有 已由用户重新定义。
答案 4 :(得分:1)
您的示例可以通过以下方式运行:( with 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
答案 5 :(得分:1)
解释此规则的方法是,您不能在最终链接对象(可执行文件)中拥有函数的多个定义。因此,如果链接中包含的所有对象只有一个函数的定义,那么你就是好的。牢记这一点,请考虑以下情况。
答案 6 :(得分:1)
我经常使用这个:
void my_strcpy(char *dest, char *src)
{
int i;
i = 0;
while (src[i])
{
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
你也可以通过修改一行
来做strncpyvoid 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';
}