I passed a pointer to glVertexAttribPointer without binded buffer and it works

I’m reading about how glVertexAttribPointer works.

Here’s my code that renders a video:

void OpenGLArea::glInit()
{
	int frameWidth = 640;
	int frameHeight = 360;
	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

	Shader vertex_shader(ShaderType::Vertex, "vertex.shader");
	Shader fragment_shader(ShaderType::Fragment, "fragment.shader");

	program = new Program();
	program->attach_shader(vertex_shader);
	program->attach_shader(fragment_shader);

	program->link();
	unis[0] = glGetUniformLocation(program->get_id(), "tex_y");
	unis[1] = glGetUniformLocation(program->get_id(), "tex_u");
	unis[2] = glGetUniformLocation(program->get_id(), "tex_v");

	glGenTextures(3, texs);//TODO: delete texture

	//Y
	glBindTexture(GL_TEXTURE_2D, texs[0]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth, frameHeight, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//U
	glBindTexture(GL_TEXTURE_2D, texs[1]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth / 2, frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//V
	glBindTexture(GL_TEXTURE_2D, texs[2]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth / 2, frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

}

void OpenGLArea::glDraw()
{
	//glClear(GL_COLOR_BUFFER_BIT);
	program->use();

	//GLint originTextureUnit;
	//glGetIntegerv(GL_ACTIVE_TEXTURE, &originTextureUnit);

	glVertexAttribPointer(VERTEX_POINTER, 2, GL_FLOAT, 0, 0, vertices);
	glEnableVertexAttribArray(VERTEX_POINTER);

	glVertexAttribPointer(FRAGMENT_POINTER, 2, GL_FLOAT, 0, 0, textureCoordinates);
	glEnableVertexAttribArray(FRAGMENT_POINTER);


	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texs[0]);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frameWidth, frameHeight, GL_RED, GL_UNSIGNED_BYTE, buffer[0]);
	glUniform1i(unis[0], 0);

	glActiveTexture(GL_TEXTURE0 + 1);
	glBindTexture(GL_TEXTURE_2D, texs[1]);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frameWidth / 2, frameHeight / 2, GL_RED, GL_UNSIGNED_BYTE, buffer[1]);
	glUniform1i(unis[1], 1);

	glActiveTexture(GL_TEXTURE0 + 2);
	glBindTexture(GL_TEXTURE_2D, texs[2]);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frameWidth / 2, frameHeight / 2, GL_RED, GL_UNSIGNED_BYTE, buffer[2]);
	glUniform1i(unis[2], 2);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	
}

On the documentation of glVertexAttribPointer, it says:

If pointer is not NULL, a non-zero named buffer object must be bound to the GL_ARRAY_BUFFER target (see glBindBuffer), otherwise an error is generated. pointer is treated as a byte offset into the buffer object’s data store. The buffer object binding (GL_ARRAY_BUFFER_BINDING) is saved as generic vertex attribute array state (GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING) for index index.

As you see, I passed a non null pointer, but I didn’t bind this pointer to a buffer. How is it possible that my code works?

Because your code is using the compatibility profile, but the documentation you’re reading describes the core profile. The compatibility profile typically allows data to be stored either in client memory or in a buffer object, while the core profile typically requires a buffer object.