C - strlen() 似乎 return 至少 6
C - strlen() seems to return a minumum of 6
以下是 CS50 课程练习的一部分。完整的问题在这里描述:
https://cs50.harvard.edu/x/2021/psets/2/substitution/
简而言之:在命令行上,您提供了一个 26 长字母数组作为参数,这些字母将用于 'encrypt' 在运行时被提示输入的字符串,称为明文。
然后遍历明文数组,它们的 ascii 整数值(稍微简化)用于索引作为命令行参数提供的“26 个字母键”,因此 'encrypting' 初始明文字符串 (ptxt) 并将其存储在新的密文字符串 (ctxt) 中。
问题 我遇到的问题是,对于明文 比 6 短 的输入 - 我使用的 strlen() 函数将 ptxt 的长度存储在 'n' 中似乎 return 6。因此,如果我在纯文本提示符下仅键入字母 'a' - n 似乎设置为 6.
以下示例:
$ ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext: a
ciphertext: y.G[
ciphertext is 6 long
预期的输出只是 'y' ,但显然有些东西超出了范围 - 长度不应该是 6,而应该是 1。
让我发疯的是——如果你在初始化 'n' 后 取消注释 printf 语句,那么代码突然工作并且你得到以下内容:
$ ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext: a
plaintext is 1 long
ciphertext: y
ciphertext is 1 long
我在这里错过了什么?
printf 调用如何以某种方式解决这个问题?
快把我逼疯了:)
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
bool is_letter(string array);
char encrypt(string key, char c);
//Command Line input a key to 'encrypt' some plaintext inputted at runtime
int main(int argc, string argv[])
{
// if there are NOT 2 arguments OR the first argument is NOT just letters OR is not 26 letters
if (argc != 2 || !is_letter(argv[1]) || strlen(argv[1]) != 26)
{
printf("Usage: ./caesar key (where key must be 26 letters)\n");
return 1;
}
// prompt user for a plaintext string, store the length in n and initialize a ciphertext string with same length
string ptxt = get_string("plaintext: ");
int n = strlen(ptxt);
//printf("plaintext is %i long\n", n); //this is here to correct n (try commenting out this line and see what happens for ptxt < 6)
char ctxt[n];
for (int i = 0; i < n; i++)
{
ctxt[i] = encrypt(argv[1], ptxt[i]);
}
printf("ciphertext: %s\n", ctxt);
printf("ciphertext is %i long\n", (int) strlen(ctxt));
return 0;
}
// function that checks whether command line argument is all letters
bool is_letter(string array)
{
int n = strlen(array);
for (int i = 0; i < n; i++)
{
if (!isalpha(array[i])) //loop over string - if any char is not a letter - return false
{
return false;
}
}
return true; //reaching this means all chars in the string are a letter - return true
}
//function that takes a key and a char and returns the "encrypted" char
char encrypt(string key, char c)
{
if (isalpha(c))
{
int n = 0;
char letter = 0;
if (isupper(c))
{
n = c - 65;
letter = key[n];
return toupper(letter);
}
else
{
n = c - 97;
letter = key[n];
return tolower(letter);
}
}
else
{
return c;
}
}
C 中没有“字符串”这样的东西。C 中的“字符串”实际上是字节数组,char *
。 C中的数组不知道有多长,没有内置bounds checks。您要么需要知道它们的大小,要么需要一个终结器。 “字符串”以称为“空字节”的 0 结尾,通常表示为 [=13=]
.
strlen
读取字节直到它看到一个空字节。如果没有空字节,strlen
将愉快地从数组末尾进入垃圾内存,直到它碰巧看到一个空字节或 OS 阻止程序超出其内存边界,segmentation fault.
// A basic strlen() implementation.
size_t my_strlen(const char *string) {
size_t len;
// no body, just counting until it sees a null byte.
for( len = 0; string[len] != '[=10=]'; len++ );
return len;
}
(IMO CS50 通过在您学习 C 时试图隐藏这一点而造成损害。长期以来一直试图将 C 视为不是 C。裸机,热棒,没有防护 rails C 的本质不能零散地隐藏。你要么得到一团糟,要么得到一种新语言。如果你想要字符串,请使用 C++ 或完全实现的库,如 GLib。)
逐字节创建新字符串时,必须终止它。并且它必须有一个额外的字节来存储 0.
// Allocate an extra byte for the terminating null.
// At this point ctxt contains garbage.
char ctxt[n+1];
for (int i = 0; i < n; i++)
{
ctxt[i] = encrypt(argv[1], ptxt[i]);
}
// Terminate the string.
ctxt[n] = '[=11=]';
And how is it that a printf call somehow remedies this issue?
当你像char ctxt[n+1]
一样分配内存时,它未初始化。它不会自动归零。它包含该内存中的所有垃圾。你可能会走运并得到全零。它可以包含其他字符串。它可能包含看起来像随机垃圾的东西。
在分配 ctxt
之前添加一个 printf
会稍微改变分配给 ctxt
的内存块。 printf
还必须分配内存,因此 ctxt
可能会得到一个略有不同的内存块,恰好以零开头。 ctxt
可能会获得 printf 分配、归零和释放的内存块。由于内存是一种全局资源,程序一部分的更改可能会暴露或隐藏程序另一部分的内存错误。
valgrind and AddressSanitizer 等工具可以帮助发现这些细微的错误。
以下是 CS50 课程练习的一部分。完整的问题在这里描述: https://cs50.harvard.edu/x/2021/psets/2/substitution/
简而言之:在命令行上,您提供了一个 26 长字母数组作为参数,这些字母将用于 'encrypt' 在运行时被提示输入的字符串,称为明文。
然后遍历明文数组,它们的 ascii 整数值(稍微简化)用于索引作为命令行参数提供的“26 个字母键”,因此 'encrypting' 初始明文字符串 (ptxt) 并将其存储在新的密文字符串 (ctxt) 中。
问题 我遇到的问题是,对于明文 比 6 短 的输入 - 我使用的 strlen() 函数将 ptxt 的长度存储在 'n' 中似乎 return 6。因此,如果我在纯文本提示符下仅键入字母 'a' - n 似乎设置为 6.
以下示例:
$ ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext: a
ciphertext: y.G[
ciphertext is 6 long
预期的输出只是 'y' ,但显然有些东西超出了范围 - 长度不应该是 6,而应该是 1。 让我发疯的是——如果你在初始化 'n' 后 取消注释 printf 语句,那么代码突然工作并且你得到以下内容:
$ ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext: a
plaintext is 1 long
ciphertext: y
ciphertext is 1 long
我在这里错过了什么? printf 调用如何以某种方式解决这个问题?
快把我逼疯了:)
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
bool is_letter(string array);
char encrypt(string key, char c);
//Command Line input a key to 'encrypt' some plaintext inputted at runtime
int main(int argc, string argv[])
{
// if there are NOT 2 arguments OR the first argument is NOT just letters OR is not 26 letters
if (argc != 2 || !is_letter(argv[1]) || strlen(argv[1]) != 26)
{
printf("Usage: ./caesar key (where key must be 26 letters)\n");
return 1;
}
// prompt user for a plaintext string, store the length in n and initialize a ciphertext string with same length
string ptxt = get_string("plaintext: ");
int n = strlen(ptxt);
//printf("plaintext is %i long\n", n); //this is here to correct n (try commenting out this line and see what happens for ptxt < 6)
char ctxt[n];
for (int i = 0; i < n; i++)
{
ctxt[i] = encrypt(argv[1], ptxt[i]);
}
printf("ciphertext: %s\n", ctxt);
printf("ciphertext is %i long\n", (int) strlen(ctxt));
return 0;
}
// function that checks whether command line argument is all letters
bool is_letter(string array)
{
int n = strlen(array);
for (int i = 0; i < n; i++)
{
if (!isalpha(array[i])) //loop over string - if any char is not a letter - return false
{
return false;
}
}
return true; //reaching this means all chars in the string are a letter - return true
}
//function that takes a key and a char and returns the "encrypted" char
char encrypt(string key, char c)
{
if (isalpha(c))
{
int n = 0;
char letter = 0;
if (isupper(c))
{
n = c - 65;
letter = key[n];
return toupper(letter);
}
else
{
n = c - 97;
letter = key[n];
return tolower(letter);
}
}
else
{
return c;
}
}
C 中没有“字符串”这样的东西。C 中的“字符串”实际上是字节数组,char *
。 C中的数组不知道有多长,没有内置bounds checks。您要么需要知道它们的大小,要么需要一个终结器。 “字符串”以称为“空字节”的 0 结尾,通常表示为 [=13=]
.
strlen
读取字节直到它看到一个空字节。如果没有空字节,strlen
将愉快地从数组末尾进入垃圾内存,直到它碰巧看到一个空字节或 OS 阻止程序超出其内存边界,segmentation fault.
// A basic strlen() implementation.
size_t my_strlen(const char *string) {
size_t len;
// no body, just counting until it sees a null byte.
for( len = 0; string[len] != '[=10=]'; len++ );
return len;
}
(IMO CS50 通过在您学习 C 时试图隐藏这一点而造成损害。长期以来一直试图将 C 视为不是 C。裸机,热棒,没有防护 rails C 的本质不能零散地隐藏。你要么得到一团糟,要么得到一种新语言。如果你想要字符串,请使用 C++ 或完全实现的库,如 GLib。)
逐字节创建新字符串时,必须终止它。并且它必须有一个额外的字节来存储 0.
// Allocate an extra byte for the terminating null.
// At this point ctxt contains garbage.
char ctxt[n+1];
for (int i = 0; i < n; i++)
{
ctxt[i] = encrypt(argv[1], ptxt[i]);
}
// Terminate the string.
ctxt[n] = '[=11=]';
And how is it that a printf call somehow remedies this issue?
当你像char ctxt[n+1]
一样分配内存时,它未初始化。它不会自动归零。它包含该内存中的所有垃圾。你可能会走运并得到全零。它可以包含其他字符串。它可能包含看起来像随机垃圾的东西。
在分配 ctxt
之前添加一个 printf
会稍微改变分配给 ctxt
的内存块。 printf
还必须分配内存,因此 ctxt
可能会得到一个略有不同的内存块,恰好以零开头。 ctxt
可能会获得 printf 分配、归零和释放的内存块。由于内存是一种全局资源,程序一部分的更改可能会暴露或隐藏程序另一部分的内存错误。
valgrind and AddressSanitizer 等工具可以帮助发现这些细微的错误。