stella
stella

Reputation: 2596

How to avoid providing length along with char*?

There is a function which sends data to the server:

int send(
  _In_  SOCKET s,
  _In_  const char *buf,
  _In_  int len,
  _In_  int flags
);

Providing length seems to me a little bit weird. I need to write a function, sending a line to the server and wrapping this one such that we don't have to provide length explicitly. I'm a Java-developer and in Java we could just invoke String::length() method, but now we're not in Java. How can I do that, unless providing length as a template parameter? For instance:

void sendLine(SOCKET s, const char *buf)
{
}

Is it possible to implement such a function?

Upvotes: 0

Views: 112

Answers (3)

Christian Hackl
Christian Hackl

Reputation: 27528

Edit: My answer originally only mentioned std::string. I've now also added std::vector<char> to account for situations where send is not used for strictly textual data.


First of all, you absolutely need a C++ book. You are looking for either the std::string class or for std::vector<char>, both of which are fundamental elements of the language.

Your question is a bit like asking, in Java, how to avoid char[] because you never heard of java.lang.String, or how to avoid arrays in general because you never heard of java.util.ArrayList.


For the first part of this answer, let's assume you are dealing with just text output here, i.e. with output where a char is really meant to be a text character. That's the std::string use case.

Providing lenght seems to me a little bit wierd.

That's the way strings work in C. A C string is really a pointer to a memory location where characters are stored. Normally, C strings are null-terminated. This means that the last character stored for the string is '\0'. It means "the string stops here, and if you move further, you enter illegal territory".

Here is a C-style example:

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

void f(char const* s)
{
    int l = strlen(s); // l = 3
    printf(s); // prints "foo"
}

int main()
{
    char* test = new char[4]; // avoid new[] in real programs
    test[0] = 'f';
    test[1] = 'o';
    test[2] = 'o';
    test[3] = '\0';
    f(test);
    delete[] test;
}

strlen just counts all characters at the specified position in memory until it finds '\0'. printf just writes all characters at the specified position in memory until it finds '\0'.

So far, so good. Now what happens if someone forgets about the null terminator?

char* test = new char[3]; // don't do this at home, please
test[0] = 'f';
test[1] = 'o';
test[2] = 'o';
f(test); // uh-oh, there is no null terminator...

The result will be undefined behaviour. strlen will keep looking for '\0'. So will printf. The functions will try to read memory they are not supposed to. The program is allowed to do anything, including crashing. The evil thing is that most likely, nothing will happen for a while because a '\0' just happens to be stored there in memory, until one day you are not so lucky anymore.

That's why C functions are sometimes made safer by requiring you to explicitly specify the number of characters. Your send is such a function. It works fine even without null-terminated strings.

So much for C strings. And now please don't use them in your C++ code. Use std::string. It is designed to be compatible with C functions by providing the c_str() member function, which returns a null-terminated char const * pointing to the contents of the string, and it of course has a size() member function to tell you the number of characters without the null-terminated character (e.g. for a std::string representing the word "foo", size() would be 3, not 4, and 3 is also what a C function like yours would probably expect, but you have to look at the documentation of the function to find out whether it needs the number of visible characters or number of elements in memory).

In fact, with std::string you can just forget about the whole null-termination business. Everything is nicely automated. std::string is exactly as easy and safe to use as java.lang.String.

Your sendLine should thus become:

void sendLine(SOCKET s, std::string const& line)
{
    send(s, line.c_str(), line.size());
}

(Passing a std::string by const& is the normal way of passing big objects in C++. It's just for performance, but it's such a widely-used convention that your code would look strange if you just passed std::string.)

How can I do that, unless providing lenght as a template parameter?

This is a misunderstanding of how templates work. With a template, the length would have to be known at compile time. That's certainly not what you intended.


Now, for the second part of the answer, perhaps you aren't really dealing with text here. It's unlikely, as the name "sendLine" in your example sounds very much like text, but perhaps you are dealing with raw data, and a char in your output does not represent a text character but just a value to be interpreted as something completely different, such as the contents of an image file.

In that case, std::string is a poor choice. Your output could contain '\0' characters that do not have the meaning of "data ends here", but which are part of the normal contents. In other words, you don't really have strings anymore, you have a range of char elements in which '\0' has no special meaning.

For this situation, C++ offers the std::vector template, which you can use as std::vector<char>. It is also designed to be usable with C functions by providing a member function that returns a char pointer. Here's an example:

void sendLine(SOCKET s, std::vector<char> const& data)
{
    send(s, &data[0], data.size());
}

(The unusual &data[0] syntax means "pointer to the first element of the encapsulated data. C++11 has nicer-to-read ways of doing this, but &data[0] also works in older versions of C++.)


Things to keep in mind:

  • std::string is like String in Java.
  • std::vector is like ArrayList in Java.
  • std::string is for a range of char with the meaning of text, std::vector<char> is for a range of char with the meaning of raw data.
  • std::string and std::vector are designed to work together with C APIs.
  • Do not use new[] in C++.
  • Understand the null termination of C strings.

Upvotes: 3

MikeMB
MikeMB

Reputation: 21156

Use std string:

void sendLine(SOCKET s, const std::string& buf) {
    send (s, buf.c_str(), buf.size()+1, 0); //+1 will also transmit terminating \0.
 }

On a side note: your wrapper function ignores the return value and doesn't take any flags.

Upvotes: 5

David Haim
David Haim

Reputation: 26486

you can retrieve the length of C-string by using strlen(const char*) function. make sure all the strings are null terminated and keep in mind that null-termination (the length grows by 1)

Upvotes: 3

Related Questions