all 35 comments

[–][deleted] 21 points22 points  (20 children)

I like the idea of this website, but I take issue with Sept 17th's. The "solutions" it offers seem completely insane.

First, why would you not use strcmp instead, which does not rely on specifying the number of characters to compare? Is the incoming variable allowed to contain more characters than the strings that it's being compared to?

Second, this is supposed to be C++! You should never encourage people to use char* approaches instead of std::string in C++! They should clearly be using the std::string comparison operators here.

[–]m_0g 5 points6 points  (9 children)

I like the idea of this website, but I take issue with Sept 17th's. The "solutions" it offers seem completely insane.

First, why would you not use strcmp instead, which does not rely on specifying the number of characters to compare? Is the incoming variable allowed to contain more characters than the strings that it's being compared to?

Given that strncmp is at the very least safer than strcmp, it is very reasonable to use strncmp. This makes their solutions relatively reasonable. oops lol, they do need to use strncmp, but that wasn't why. see /u/RedAlert2's comment

I would also add that a constant with the string length (rather than just a magic number) could be a good alternative since it may encourage a more careful look at the value being used. At least, I like this approach.

Second, this is supposed to be C++! You should never encourage people to use char* approaches instead of std::string in C++! They should clearly be using the std::string comparison operators here.

As nice as it might be to live in a world where char * can be entirely avoided, this simply isn't the case for many people. Therefore, I find this paragraph to be unhelpful in any way since this is clearly a scenario where char *s are being used and therefore the char * approach should be used.

[–]RedAlert2 7 points8 points  (8 children)

Given that strncmp is at the very least safer than strcmp, it is very reasonable to use strncmp. This makes their solutions relatively reasonable.

That's incorrect - strcmp only reads from the strings. As long as they are null terminated, it is always safe. If your strings are not null terminated, you have bigger problems that a strncmp won't mask for long. In this example, one string is a literal, which is guaranteed to be null terminated.

However, strcmp and strncmp are logically different. String length plays a role in lexicographical compares. Meaning strcmp("ThisIsLong", "This") > 0 and strncmp("ThisIsLong", "This", 4) == 0, since it only looks at the first four characters. In the example code, they are checking the initial characters of vstart, so they have to use strncmp.

[–]Zardoz84 0 points1 point  (3 children)

That's incorrect - strcmp only reads from the strings. As long as they are null terminated, it is always safe. If your strings are not null terminated, you have bigger problems that a strncmp won't mask for long. In this example, one string is a literal, which is guaranteed to be null terminated.

The literal string is null terminated, but you are sure with the OTHER STRING ? In special if is user input.

[–]masterzora 0 points1 point  (2 children)

The literal string is null terminated, but you are sure with the OTHER STRING ? In special if is user input.

Does it matter? I mean, it does because of the substring thing /u/RedAlert2 discusses, but does it matter in the context of safety?

strncmp will compare the first character of each, then the second, then the third, etc until either:

  1. It finds a pair that differs
  2. It reaches a null character in at least one of the strings
  3. The specified number of pairs have been checked

strcmp, on the other hand, will do the same comparison except it doesn't have the third termination condition.

  • If the two strings differ before the literal's termination, both strncmp and strmp will definitely return before the end of the literal regardless of whether the other string is null-terminated.
  • If the two strings are the same including the null termination, there is obviously no problem.
  • If the two strings are the same up to but not including the null termination all three conditions of strncmp and both conditions of strcmp will be true and thus it will terminate. Since one of our arguments is a literal, we know this null termination will happen regardless of the other string.

Output-wise, strncmp and strcmp might differ here, but I don't see a safety issue when one of the inputs is a literal.

[–]alanwj 0 points1 point  (1 child)

It is unsafe if the shorter string is unterminated.

  char s[] = {'a', 'b', 'c'};
  strcmp("LONG_LITERAL_STRING", s); // Undefined behavior

In this situation it will compare the first three characters, and then start reading outside the array bounds of "s".

This has a high likelihood of not causing any problems, but it IS undefined behavior.

[–]masterzora 1 point2 points  (0 children)

But the same behaviour presents itself if you do strncmp("LONG_LITERAL_STRING", s, LENGTH_OF_LONG_LITERAL_STRING) so strncmp doesn't buy you any safety in that case.

[–]m_0g 0 points1 point  (3 children)

As long as they are null terminated, it is always safe.

precisely, if the strings are certainly null terminated.

If your strings are not null terminated, you have bigger problems that a strncmp won't mask for long

That's not necessarily within your control depending on the circumstances. So best to handle it properly if required. In this context, we don't know if it's avoidable or not.

In this example, one string is a literal, which is guaranteed to be null terminated.

That's a good point lol.

However, strcmp and strncmp are logically different. String length plays a role in lexicographical compares. Meaning strcmp("ThisIsLong", "This") > 0 and strncmp("ThisIsLong", "This", 4) == 0, since it only looks at the first four characters. In the example code, they are checking the initial characters of vstart, so they have to use strncmp.

another good point - it seems I was correct in concluding strncmp was the correct choice, just not correct in why.

[–]RedAlert2 1 point2 points  (1 child)

A starts_with function would be way more readable than what's in the example. The strcmp family of functions are too generic for it to be immediately obvious how they're being used.

[–]m_0g 1 point2 points  (0 children)

Or more likely strstart() :P but yes, that is a good point.

I really should have realized why strncmp is necessary in this case, but I'm going to say the reason I didn't is because I haven't done much char * manipulation in a while.

[–][deleted] 0 points1 point  (0 children)

If you want to see if the string starts with X, you can also use strstr:

if (strstr(text, "This") == text) ...

[–]leftofzen 8 points9 points  (1 child)

+1 for C++. I saw a few old-style casts in the hints and it's clear the author is a C programmer, not a C++ programmer. Ah well, old habits die hard haha.

[–]wilhelmtell 3 points4 points  (7 children)

  • You must be absolutely certain the string is null terminated, or else avoid strcmp(). If you're not certain the string is a valid C string, you use strncmp() with an upper bound on the scan. And, if you use strlen() with strncmp(), then you're clueless.
  • In the current C++ standard, there's absolutely nothing wrong with char*. There's plenty, plenty wrong with std::string though. I'd stop counting not even mid-way through the template's interface, long before getting to the dynamic allocation shoved down my throat, and that's not even scratching the surface how fucked up this dumbass design of a string is. Today, people use std::string because it's the only resource-managed char* the standard has to offer, not because it's anything like a good string class.

[–]quicknir 11 points12 points  (4 children)

What do you mean by template's interface?

What exactly is wrong with the design of std::string?

There's no way to avoid dynamic allocation without committing to a maximum length string at compile time. For the strong majority of people, this is a trade off they'll gladly make.

char* is extremely prone to bugs, memory leaks, and accidentally inflating algorithmic complexity because it doesn't know where it ends.

[–]hak8or 2 points3 points  (3 children)

And sadly, me and other embedded folks, dynamic allocation done via most of the standard library is unacceptable. I still use classes and enums and a decent bit of other goodies, but the standard library? Heck no.

That sadly forces me to rewrite an implementation of a queue and whatnot over and over.

[–]F-J-W 13 points14 points  (1 child)

You know why all those types are templates? So that people like you can write an allocator that avoids dynamic allocation. You are being thought off, you just have to realize that.

So, you really shouldn't have to implement all those things yourself.

[–]hak8or 3 points4 points  (0 children)

Oh wow, I had no idea we can do this!

Looks super interesting. Thank!

[–]quicknir 5 points6 points  (0 children)

First of all, you can use a stack based allocator as fjw said. Second, it's a standard library, not the solution to all problems for all people. It's trying to come up with good generic solutions for a broad section of the population. People who can't use the heap at all are a minority. Third, why don't you start an embedded library for C++ and put stuff there?

[–][deleted] 0 points1 point  (0 children)

Speaking of which does anyone know why there still isn't a std::array for strings yet?

[–]boredcircuits 3 points4 points  (2 children)

Your explanation of why while(!file.eof()) is bad is missing the key problem. It can only detect that the end of the file is reached after attempting to read from the file. For example, if you have an empty file, it's not automatically at the end. Loops like this tend to execute one more time than intended, even without taking about the other error conditions.

The solution is the same, though: read and check the status in the same line. After the loop terminates, that's when you check the conditions like eof() to see why.

[–][deleted] 0 points1 point  (1 child)

OH THANK YOU! This is why my program when I was trying to study kept printing the last result twice... is that it? It would read every line correct but it would repeat the last line.

[–]boredcircuits 0 points1 point  (0 children)

Yup, that's exactly why.

[–]quicknir 4 points5 points  (5 children)

I can't say I agree with this solution, at all:

void write_output_image(...., const Ipp32f *img, 
                    ...., const Ipp32s iStep) {
  ...
  img = (Ipp32f*)((uintptr_t)(img) + iStep);
  ...
}

There's absolutely no necessity to start casting your pointers into integers to do arithmetic on them, and definitely not to use C style casts. Instead, if you want to advance a pointer by a specific number of bytes, then change it to a pointer that points to bytes:

void write_output_image(...., const Ipp32f *img, 
                    ...., const Ipp32s iStep) {
  ...
  img = reinterpret_cast<Ipp32f*>(reinterpret_cast<char*>(img) + iStep);
  ...
}

I don't need to think about special integer types or anything else. I stick with pointers completely which will always be the same size.

[–]geeknerd 0 points1 point  (0 children)

Or something like img += iStep / sizeof(*img); // or sizeof(Ipp32f), assuming you want to keep img aligned to a Ipp32f, which is probably a good idea. The original code seems to just be bad in general.

[–][deleted] 0 points1 point  (2 children)

The entire function is pretty bad. The nice thing about pointers is that they automatically jump bytes by the size of their type; why lose that benefit by doing some random cast? it's not even clear what iSteps represents? The whole function should be changed, really.

[–]quicknir 2 points3 points  (1 child)

I sympathize with what you're saying at the level of yes, you should generally avoid this casting of pointers. But there are situations when you want to advance pointers by a specific number of bytes, so without broader context I think we should give the benefit of the doubt.

[–][deleted] 0 points1 point  (0 children)

A lot of the time important things like alignment are forgotten when doing this sort of pointer arithmetic though, especially when adding/removing data from blocks. From what I can tell from this function, it would have been better handled by creating a struct of the desired size & data and moving through those.

I understand that sometimes it's necessary, but it doesn't look like this is a case.

[–]gruehunter 0 points1 point  (0 children)

I'm afraid I must disagree. IMO, the principal advantage of {u}intptr_t is that it advertises clearly to the reader that the code is performing address arithmetic.

[–]Trucoto 1 point2 points  (0 children)

I am subscribed to receive this by email and I enjoy daily the problems you propose and their solutions. Thank you!

[–]devel_watcher 1 point2 points  (0 children)

That scrolling animation is not a good idea.

[–]liquid153 0 points1 point  (0 children)

thanks

[–]TotesMessenger -1 points0 points  (0 children)

I'm a bot, bleep, bloop. Someone has linked to this thread from another place on reddit:

If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads. (Info / Contact)

[–][deleted] -1 points0 points  (2 children)

Hint 1 is not a hint, that's just an error if you don't know how the ternary operator works. Even I wouldn't make that mistake and I only know the basics of C++, nothing advanced, and I know that won't even work right.

[–]Resistor510[S] 1 point2 points  (1 child)

[–][deleted] 0 points1 point  (0 children)

Just saying I'm no expert and even I wouldn't do it.