这是家庭作业。我想实现#include <stdio.h>
#include <string.h>
int get_width(char* word) {
return 15 - strlen(word);
}
struct category {
char *name;
int namelen;
int count;
};
struct category createCat(char *name) {
struct category cat = {name, get_width(name), 0};
return cat;
}
int main () {
int c;
int inside_word;
int i;
int p;
struct category words = createCat("words");
struct category lines = createCat("lines");
struct category characters = createCat("characters");
struct category catList[] = {words, lines, characters};
while ((c = getchar()) != EOF) {
characters.count++;
if (c != '\n' && c != ' ' && c != '\t') {
putchar(c);
if (!inside_word) {
inside_word = 1;
words.count++;
}
}
else {
if (inside_word)
printf("\n");
inside_word = 0;
if (c == '\n')
lines.count++;
}
}
printf("\n%d words, %d lines, %d characters\n",
words.count, lines.count, characters.count);
for (i = 0; i < 3; i++) {
printf("%s:%*s", catList[i].name, catList[i].namelen, " ");
printf("%d", catList[i].count);
for ( p = 0; p < catList[p].count; p++)
printf("#");
printf("\n");
}
return 0;
}
。我被告知记忆区域不能重叠。实际上我不明白这意味着什么,因为这段代码工作正常,但存在内存重叠的可能性。怎么预防呢?
memcpy()
答案 0 :(得分:2)
实际上我不明白什么意思是[重叠记忆]
考虑这个例子:
char data[100];
memcpy(&data[5], &data[0], 95);
从程序的角度来看,src
到src+n
的地址范围不得与dest
到dest+n
的范围重叠。
如果存在内存重叠的可能性,如何防止它?
如果src
的数字地址低于dest
,则可以决定从后面复制重叠区域,从而使算法有效或不重叠。
注意:由于您正在执行memcpy
,而不是strcpy
,因此newdest[i]='\0'
强制空终止是不正确的,需要将其删除。
答案 1 :(得分:2)
当源和目标内存块重叠时,如果循环从索引0
开始复制一个元素,则它适用于dest < source
,但不适用于dest > source
(因为你在复制之前覆盖元素),反之亦然。
您的代码从索引0
开始复制,因此您可以简单地测试哪些情况有效,哪些无效。请参阅以下测试代码;它显示了如何向前移动测试字符串失败,而向后移动字符串工作正常。此外,它还显示了从后向复制时如何正向移动测试字符串:
#include <stdio.h>
#include <string.h>
void *mem_copy(void *dest, const void *src, size_t n) {
size_t i = 0;
char* newsrc = (char*)src;
char* newdest = (char*)dest;
while(i < n) {
newdest[i] = newsrc[i];
i++;
}
return newdest;
}
void *mem_copy_from_backward(void *dest, const void *src, size_t n) {
size_t i;
char* newsrc = (char*)src;
char* newdest = (char*)dest;
for (i = n; i-- > 0;) {
newdest[i] = newsrc[i];
}
return newdest;
}
int main() {
const char* testcontent = "Hello world!";
char teststr[100] = "";
printf("move teststring two places forward:\n");
strcpy(teststr, testcontent);
size_t length = strlen(teststr);
printf("teststr before mem_copy: %s\n", teststr);
mem_copy(teststr+2, teststr, length+1);
printf("teststr after mem_copy: %s\n", teststr);
printf("\nmove teststring two places backward:\n");
strcpy(teststr, testcontent);
length = strlen(teststr);
printf("teststr before mem_copy: %s\n", teststr);
mem_copy(teststr, teststr+2, length+1);
printf("teststr after mem_copy: %s\n", teststr);
printf("move teststring two places forward using copy_from_backward:\n");
strcpy(teststr, testcontent);
length = strlen(teststr);
printf("teststr before mem_copy: %s\n", teststr);
mem_copy_from_backward(teststr+2, teststr, length+1);
printf("teststr after mem_copy: %s\n", teststr);
}
输出:
move teststring two places forward:
teststr before mem_copy: Hello world!
teststr after mem_copy: HeHeHeHeHeHeHeH
move teststring two places backward:
teststr before mem_copy: Hello world!
teststr after mem_copy: llo world!
move teststring two places forward using copy_from_backward:
teststr before mem_copy: Hello world!
teststr after mem_copy: HeHello world!
因此,可以编写一个函数,该函数根据调用者是要向前还是向后复制来决定是从索引0
还是从索引n
开始复制。棘手的是找出调用者是向前还是向后复制,因为src
上的指针算术和dest
if (src < dest) copy_from_backward(...)
实际上不允许在每种情况下使用(参见标准) ,例如draft}:
6.5.9平等运营商
当比较两个指针时,结果取决于相对值 指向的对象的地址空间中的位置。如果两个 指向对象或不完整类型的指针都指向同一个对象, 或者两者都指向同一个数组对象的最后一个元素,它们 比较平等。如果指向的对象是同一个成员 聚合对象,指向稍后声明的结构成员的指针 大于指向结构中先前声明的成员的指针, 和指向具有较大下标值的数组元素的指针进行比较 大于指向同一数组元素的指针 下标值。指向同一个union对象的成员的所有指针 比较平等。如果表达式P指向数组的元素 对象和表达式Q指向同一个的最后一个元素 数组对象,指针表达式Q + 1比大于P. In 在所有其他情况下,行为未定义。
虽然我从来没有遇到src < dest
没有给我想要的结果的情况,但是如果它们不属于同一个数组,那么比较两个指针实际上是未定义的行为。
因此,如果你问“如何防止它?”,我认为唯一正确的答案必须是:“它受调用者的影响,因为函数mem_copy
无法决定它是否可以比较{{1} }和src
正确。“
答案 2 :(得分:1)
重新实施memcpy()
时存在一些问题:
大小参数n
的类型应为size_t
。索引变量i
应与size参数具有相同的类型。
可以传递0
的计数。实际上,在这种情况下,您的代码会正常运行,从assert()
中移除测试。
除非绝对必要,否则请避免丢弃const
限定符。
请勿在目的地末尾添加'\0'
,否则会导致缓冲区溢出。
以下是更正后的版本:
void *mem_copy(void *dest, const void *src, size_t n) {
assert(n == 0 || (src != NULL && dest != NULL));
size_t i = 0;
const char *newsrc = (const char *)src;
char *newdest = (char *)dest;
while (i < n) {
newdest[i] = newsrc[i];
i++;
}
return dest;
}
关于源区域和目标区域之间的潜在重叠,如果目标指针大于源,但在源区域内,则代码的行为会令人惊讶:
char buffer[10] = "12345";
printf("before: %s\n", buffer);
mem_copy(buffer + 1, buffer, 5);
printf("after: %s\n", buffer);
将输出:
before: 12345
after: 111111
没有完全可移植的方法来测试这种重叠,但是在非常外的架构上,执行时间和代码大小的成本很低。 memcpy()
的语义是假定库不执行此类测试,因此如果源和目标区域不可能重叠,程序员应该只调用此函数。如有疑问,请使用正确处理重叠区域的memmove()
。
如果你想为此添加一个assert
,这里有一个最便携的:
assert(n == 0 || newdest + n <= newsrc || newdest >= newsrc + n);
这是memmove()
的简单重写,虽然不是完全可移植的:
void *mem_move(void *dest, const void *src, size_t n) {
assert(n == 0 || (src != NULL && dest != NULL));
const char *newsrc = (const char *)src;
char *newdest = (char *)dest;
if (newdest <= newsrc || newdest >= newsrc + n) {
/* Copying forward */
for (size_t i = 0; i < n; i++) {
newdest[i] = newsrc[i];
}
} else {
/* Copying backwards */
for (size_t i = n; i-- > 0;) {
newdest[i] = newsrc[i];
}
}
return dest;
}